diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 52fe868d..ff1e01ed 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,7 @@ build apk: - rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9 - ln -s ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21 ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9 - cp -f $CI_PROJECT_DIR/scripts/build-target-python.sh ~/.buildozer/android/crystax-ndk-10.3.2/build/tools/build-target-python.sh + - cp -f $CI_PROJECT_DIR/scripts/mangled-glibc-syscalls.h ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21/arch-arm/usr/include/crystax/bionic/libc/include/sys/mangled-glibc-syscalls.h - rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz - git secret reveal - mv buildozer.spec.travis buildozer.spec diff --git a/app/src/page/splash/view.js b/app/src/page/splash/view.js index defbbf0d..ae207f44 100644 --- a/app/src/page/splash/view.js +++ b/app/src/page/splash/view.js @@ -63,10 +63,49 @@ class SplashScreen extends React.PureComponent { }); } + navigateToMain = () => { + const { navigation } = this.props; + const resetAction = StackActions.reset({ + index: 0, + actions: [ + NavigationActions.navigate({ routeName: 'Main'}) + ] + }); + navigation.dispatch(resetAction); + + const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl; + if (launchUrl) { + if (launchUrl.startsWith('lbry://?verify=')) { + let verification = {}; + try { + verification = JSON.parse(atob(launchUrl.substring(15))); + } catch (error) { + console.log(error); + } + if (verification.token && verification.recaptcha) { + AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true'); + try { + verifyUserEmail(verification.token, verification.recaptcha); + } catch (error) { + const message = 'Invalid Verification Token'; + verifyUserEmailFailure(message); + notify({ message }); + } + } else { + notify({ + message: 'Invalid Verification URI', + }); + } + } else { + navigateToUri(navigation, launchUrl); + } + } + } + componentWillReceiveProps(nextProps) { const { emailToVerify, - navigation, + getSync, setEmailToVerify, verifyUserEmail, verifyUserEmailFailure @@ -81,41 +120,15 @@ class SplashScreen extends React.PureComponent { } // user is authenticated, navigate to the main view - const resetAction = StackActions.reset({ - index: 0, - actions: [ - NavigationActions.navigate({ routeName: 'Main'}) - ] - }); - navigation.dispatch(resetAction); - - const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl; - if (launchUrl) { - if (launchUrl.startsWith('lbry://?verify=')) { - let verification = {}; - try { - verification = JSON.parse(atob(launchUrl.substring(15))); - } catch (error) { - console.log(error); - } - if (verification.token && verification.recaptcha) { - AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true'); - try { - verifyUserEmail(verification.token, verification.recaptcha); - } catch (error) { - const message = 'Invalid Verification Token'; - verifyUserEmailFailure(message); - notify({ message }); - } - } else { - notify({ - message: 'Invalid Verification URI', - }); - } - } else { - navigateToUri(navigation, launchUrl); - } + if (user.has_verified_email) { + NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => { + getSync(walletPassword); + this.navigateToMain(); + }); + return; } + + this.navigateToMain(); }); }); } diff --git a/buildozer.spec.sample b/buildozer.spec.sample index c6fba0ef..09778b38 100644 --- a/buildozer.spec.sample +++ b/buildozer.spec.sample @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.travis b/buildozer.spec.travis index c6fba0ef..09778b38 100644 --- a/buildozer.spec.travis +++ b/buildozer.spec.travis @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.vagrant b/buildozer.spec.vagrant index c6fba0ef..09778b38 100644 --- a/buildozer.spec.vagrant +++ b/buildozer.spec.vagrant @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy -requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve +requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/p4a/pythonforandroid/archs.py b/p4a/pythonforandroid/archs.py index 8f19485a..09ebba4f 100644 --- a/p4a/pythonforandroid/archs.py +++ b/p4a/pythonforandroid/archs.py @@ -1,10 +1,10 @@ -from os.path import (join, dirname) -from os import environ, uname -import sys from distutils.spawn import find_executable +from os import environ +from os.path import (exists, join, dirname, split) +from glob import glob -from pythonforandroid.logger import warning from pythonforandroid.recipe import Recipe +from pythonforandroid.util import BuildInterruptingException, build_platform class Arch(object): @@ -19,6 +19,12 @@ class Arch(object): super(Arch, self).__init__() self.ctx = ctx + # Allows injecting additional linker paths used by any recipe. + # This can also be modified by recipes (like the librt recipe) + # to make sure that some sort of global resource is available & + # linked for all others. + self.extra_global_link_paths = [] + def __str__(self): return self.arch @@ -30,24 +36,65 @@ class Arch(object): d.format(arch=self)) for d in self.ctx.include_dirs] - def get_env(self, with_flags_in_cc=True): + @property + def target(self): + target_data = self.command_prefix.split('-') + return '-'.join( + [target_data[0], 'none', target_data[1], target_data[2]]) + + def get_env(self, with_flags_in_cc=True, clang=False): env = {} - env["CFLAGS"] = " ".join([ - "-DANDROID", "-mandroid", "-fomit-frame-pointer", - "--sysroot", self.ctx.ndk_platform]) + cflags = [ + '-DANDROID', + '-fomit-frame-pointer', + '-D__ANDROID_API__={}'.format(self.ctx.ndk_api)] + if not clang: + cflags.append('-mandroid') + else: + cflags.append('-target ' + self.target) + toolchain = '{android_host}-{toolchain_version}'.format( + android_host=self.ctx.toolchain_prefix, + toolchain_version=self.ctx.toolchain_version) + toolchain = join(self.ctx.ndk_dir, 'toolchains', toolchain, + 'prebuilt', build_platform) + cflags.append('-gcc-toolchain {}'.format(toolchain)) + + env['CFLAGS'] = ' '.join(cflags) + + # Link the extra global link paths first before anything else + # (such that overriding system libraries with them is possible) + env['LDFLAGS'] = ' ' + " ".join([ + "-L'" + l.replace("'", "'\"'\"'") + "'" # no shlex.quote in py2 + for l in self.extra_global_link_paths + ]) + ' ' + + sysroot = join(self.ctx._ndk_dir, 'sysroot') + if exists(sysroot): + # post-15 NDK per + # https://android.googlesource.com/platform/ndk/+/ndk-r15-release/docs/UnifiedHeaders.md + env['CFLAGS'] += ' -isystem {}/sysroot/usr/include/{}'.format( + self.ctx.ndk_dir, self.ctx.toolchain_prefix) + env['CFLAGS'] += ' -I{}/sysroot/usr/include/{}'.format( + self.ctx.ndk_dir, self.command_prefix) + else: + sysroot = self.ctx.ndk_platform + env['CFLAGS'] += ' -I{}'.format(self.ctx.ndk_platform) + env['CFLAGS'] += ' -isysroot {} '.format(sysroot) + env['CFLAGS'] += '-I' + join(self.ctx.get_python_install_dir(), + 'include/python{}'.format( + self.ctx.python_recipe.version[0:3]) + ) + + env['LDFLAGS'] += '--sysroot={} '.format(self.ctx.ndk_platform) env["CXXFLAGS"] = env["CFLAGS"] - env["LDFLAGS"] = " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)]) + env["LDFLAGS"] += " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)]) if self.ctx.ndk == 'crystax': env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch) - py_platform = sys.platform - if py_platform in ['linux2', 'linux3']: - py_platform = 'linux' - toolchain_prefix = self.ctx.toolchain_prefix toolchain_version = self.ctx.toolchain_version command_prefix = self.command_prefix @@ -63,53 +110,71 @@ class Arch(object): env['NDK_CCACHE'] = self.ctx.ccache env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')}) - cc = find_executable('{command_prefix}-gcc'.format( - command_prefix=command_prefix), path=environ['PATH']) + if clang: + llvm_dirname = split( + glob(join(self.ctx.ndk_dir, 'toolchains', 'llvm*'))[-1])[-1] + clang_path = join(self.ctx.ndk_dir, 'toolchains', llvm_dirname, + 'prebuilt', build_platform, 'bin') + environ['PATH'] = '{clang_path}:{path}'.format( + clang_path=clang_path, path=environ['PATH']) + exe = join(clang_path, 'clang') + execxx = join(clang_path, 'clang++') + else: + exe = '{command_prefix}-gcc'.format(command_prefix=command_prefix) + execxx = '{command_prefix}-g++'.format(command_prefix=command_prefix) + + cc = find_executable(exe, path=environ['PATH']) if cc is None: print('Searching path are: {!r}'.format(environ['PATH'])) - warning('Couldn\'t find executable for CC. This indicates a ' - 'problem locating the {} executable in the Android ' - 'NDK, not that you don\'t have a normal compiler ' - 'installed. Exiting.') - exit(1) + raise BuildInterruptingException( + 'Couldn\'t find executable for CC. This indicates a ' + 'problem locating the {} executable in the Android ' + 'NDK, not that you don\'t have a normal compiler ' + 'installed. Exiting.'.format(exe)) if with_flags_in_cc: - env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format( - command_prefix=command_prefix, + env['CC'] = '{ccache}{exe} {cflags}'.format( + exe=exe, ccache=ccache, cflags=env['CFLAGS']) - env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format( - command_prefix=command_prefix, + env['CXX'] = '{ccache}{execxx} {cxxflags}'.format( + execxx=execxx, ccache=ccache, cxxflags=env['CXXFLAGS']) else: - env['CC'] = '{ccache}{command_prefix}-gcc'.format( - command_prefix=command_prefix, + env['CC'] = '{ccache}{exe}'.format( + exe=exe, ccache=ccache) - env['CXX'] = '{ccache}{command_prefix}-g++'.format( - command_prefix=command_prefix, + env['CXX'] = '{ccache}{execxx}'.format( + execxx=execxx, ccache=ccache) env['AR'] = '{}-ar'.format(command_prefix) env['RANLIB'] = '{}-ranlib'.format(command_prefix) env['LD'] = '{}-ld'.format(command_prefix) - # env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink') - # env['LDSHARED'] = env['LD'] + env['LDSHARED'] = env["CC"] + " -pthread -shared " +\ + "-Wl,-O1 -Wl,-Bsymbolic-functions " + if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax: + # For crystax python, we can't use the host python headers: + env["CFLAGS"] += ' -I{}/sources/python/{}/include/python/'.\ + format(self.ctx.ndk_dir, self.ctx.python_recipe.version[0:3]) env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix) env['MAKE'] = 'make -j5' env['READELF'] = '{}-readelf'.format(command_prefix) env['NM'] = '{}-nm'.format(command_prefix) - hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx) - - # AND: This hardcodes python version 2.7, needs fixing + hostpython_recipe = Recipe.get_recipe( + 'host' + self.ctx.python_recipe.name, self.ctx) env['BUILDLIB_PATH'] = join( hostpython_recipe.get_build_dir(self.arch), - 'build', 'lib.linux-{}-2.7'.format(uname()[-1])) + 'build', 'lib.{}-{}'.format( + build_platform, self.ctx.python_recipe.major_minor_version_string) + ) env['PATH'] = environ['PATH'] env['ARCH'] = self.arch + env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api)) if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax: env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version @@ -123,12 +188,18 @@ class ArchARM(Arch): command_prefix = 'arm-linux-androideabi' platform_dir = 'arch-arm' + @property + def target(self): + target_data = self.command_prefix.split('-') + return '-'.join( + ['armv7a', 'none', target_data[1], target_data[2]]) + class ArchARMv7_a(ArchARM): arch = 'armeabi-v7a' - def get_env(self, with_flags_in_cc=True): - env = super(ArchARMv7_a, self).get_env(with_flags_in_cc) + def get_env(self, with_flags_in_cc=True, clang=False): + env = super(ArchARMv7_a, self).get_env(with_flags_in_cc, clang=clang) env['CFLAGS'] = (env['CFLAGS'] + (' -march=armv7-a -mfloat-abi=softfp ' '-mfpu=vfp -mthumb')) @@ -142,8 +213,8 @@ class Archx86(Arch): command_prefix = 'i686-linux-android' platform_dir = 'arch-x86' - def get_env(self, with_flags_in_cc=True): - env = super(Archx86, self).get_env(with_flags_in_cc) + def get_env(self, with_flags_in_cc=True, clang=False): + env = super(Archx86, self).get_env(with_flags_in_cc, clang=clang) env['CFLAGS'] = (env['CFLAGS'] + ' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32') env['CXXFLAGS'] = env['CFLAGS'] @@ -152,12 +223,12 @@ class Archx86(Arch): class Archx86_64(Arch): arch = 'x86_64' - toolchain_prefix = 'x86' + toolchain_prefix = 'x86_64' command_prefix = 'x86_64-linux-android' - platform_dir = 'arch-x86' + platform_dir = 'arch-x86_64' - def get_env(self, with_flags_in_cc=True): - env = super(Archx86_64, self).get_env(with_flags_in_cc) + def get_env(self, with_flags_in_cc=True, clang=False): + env = super(Archx86_64, self).get_env(with_flags_in_cc, clang=clang) env['CFLAGS'] = (env['CFLAGS'] + ' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel') env['CXXFLAGS'] = env['CFLAGS'] @@ -170,8 +241,8 @@ class ArchAarch_64(Arch): command_prefix = 'aarch64-linux-android' platform_dir = 'arch-arm64' - def get_env(self, with_flags_in_cc=True): - env = super(ArchAarch_64, self).get_env(with_flags_in_cc) + def get_env(self, with_flags_in_cc=True, clang=False): + env = super(ArchAarch_64, self).get_env(with_flags_in_cc, clang=clang) incpath = ' -I' + join(dirname(__file__), 'includes', 'arm64-v8a') env['EXTRA_CFLAGS'] = incpath env['CFLAGS'] += incpath diff --git a/p4a/pythonforandroid/bootstrap.py b/p4a/pythonforandroid/bootstrap.py index ef89fef8..b4a9a9e4 100644 --- a/p4a/pythonforandroid/bootstrap.py +++ b/p4a/pythonforandroid/bootstrap.py @@ -1,17 +1,39 @@ -from os.path import (join, dirname, isdir, splitext, basename) -from os import listdir +from os.path import (join, dirname, isdir, normpath, splitext, basename) +from os import listdir, walk, sep import sh +import shlex import glob -import json import importlib +import os +import shutil from pythonforandroid.logger import (warning, shprint, info, logger, debug) from pythonforandroid.util import (current_directory, ensure_dir, - temp_directory, which) + temp_directory) from pythonforandroid.recipe import Recipe +def copy_files(src_root, dest_root, override=True): + for root, dirnames, filenames in walk(src_root): + for filename in filenames: + subdir = normpath(root.replace(src_root, "")) + if subdir.startswith(sep): # ensure it is relative + subdir = subdir[1:] + dest_dir = join(dest_root, subdir) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + src_file = join(root, filename) + dest_file = join(dest_dir, filename) + if os.path.isfile(src_file): + if override and os.path.exists(dest_file): + os.unlink(dest_file) + if not os.path.exists(dest_file): + shutil.copy(src_file, dest_file) + else: + os.makedirs(dest_file) + + class Bootstrap(object): '''An Android project template, containing recipe stuff for compilation and templated fields for APK info. @@ -27,7 +49,11 @@ class Bootstrap(object): dist_name = None distribution = None - recipe_depends = ['sdl2'] + # All bootstraps should include Python in some way: + recipe_depends = [ + ("python2", "python2legacy", "python3", "python3crystax"), + 'android', + ] can_be_chosen_automatically = True '''Determines whether the bootstrap can be chosen as one that @@ -78,6 +104,9 @@ class Bootstrap(object): def get_dist_dir(self, name): return join(self.ctx.dist_dir, name) + def get_common_dir(self): + return os.path.abspath(join(self.bootstrap_dir, "..", 'common')) + @property def name(self): modname = self.__class__.__module__ @@ -87,9 +116,10 @@ class Bootstrap(object): '''Ensure that a build dir exists for the recipe. This same single dir will be used for building all different archs.''' self.build_dir = self.get_build_dir() - shprint(sh.cp, '-r', - join(self.bootstrap_dir, 'build'), - self.build_dir) + self.common_dir = self.get_common_dir() + copy_files(join(self.bootstrap_dir, 'build'), self.build_dir) + copy_files(join(self.common_dir, 'build'), self.build_dir, + override=False) if self.ctx.symlink_java_src: info('Symlinking java src instead of copying') shprint(sh.rm, '-r', join(self.build_dir, 'src')) @@ -102,26 +132,15 @@ class Bootstrap(object): fileh.write('target=android-{}'.format(self.ctx.android_api)) def prepare_dist_dir(self, name): - # self.dist_dir = self.get_dist_dir(name) ensure_dir(self.dist_dir) def run_distribute(self): - # print('Default bootstrap being used doesn\'t know how ' - # 'to distribute...failing.') - # exit(1) - with current_directory(self.dist_dir): - info('Saving distribution info') - with open('dist_info.json', 'w') as fileh: - json.dump({'dist_name': self.ctx.dist_name, - 'bootstrap': self.ctx.bootstrap.name, - 'archs': [arch.arch for arch in self.ctx.archs], - 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules}, - fileh) + self.distribution.save_info(self.dist_dir) @classmethod def list_bootstraps(cls): '''Find all the available bootstraps and return them.''' - forbidden_dirs = ('__pycache__', ) + forbidden_dirs = ('__pycache__', 'common') bootstraps_dir = join(dirname(__file__), 'bootstraps') for name in listdir(bootstraps_dir): if name in forbidden_dirs: @@ -152,7 +171,7 @@ class Bootstrap(object): for recipe in recipes: try: recipe = Recipe.get_recipe(recipe, ctx) - except IOError: + except ValueError: conflicts = [] else: conflicts = recipe.conflicts @@ -160,7 +179,7 @@ class Bootstrap(object): for conflict in conflicts]): ok = False break - if ok: + if ok and bs not in acceptable_bootstraps: acceptable_bootstraps.append(bs) info('Found {} acceptable bootstraps: {}'.format( len(acceptable_bootstraps), @@ -249,16 +268,22 @@ class Bootstrap(object): info('Python was loaded from CrystaX, skipping strip') return 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'), + tokens = shlex.split(env['STRIP']) + strip = sh.Command(tokens[0]) + if len(tokens) > 1: + strip = strip.bake(tokens[1:]) + + libs_dir = join(self.dist_dir, '_python_bundle', + '_python_bundle', 'modules') + if self.ctx.python_recipe.name == 'python2legacy': + libs_dir = join(self.dist_dir, 'private') + filens = shprint(sh.find, libs_dir, 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'): + if not filen: + continue # skip the last '' try: strip(filen, _env=env) except sh.ErrorReturnCode_1: diff --git a/p4a/pythonforandroid/bootstraps/common/build/ant.properties b/p4a/pythonforandroid/bootstraps/common/build/ant.properties new file mode 100644 index 00000000..0dee5c8e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/ant.properties @@ -0,0 +1,22 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +source.absolute.dir = tmp-src + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/common/build/build.py b/p4a/pythonforandroid/bootstraps/common/build/build.py new file mode 100644 index 00000000..38fe2b77 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/build.py @@ -0,0 +1,795 @@ +#!/usr/bin/env python2.7 + +from __future__ import print_function + +import json +from os.path import ( + dirname, join, isfile, realpath, + relpath, split, exists, basename +) +from os import listdir, makedirs, remove +import os +import shlex +import shutil +import subprocess +import sys +import tarfile +import tempfile +import time +from zipfile import ZipFile + +from distutils.version import LooseVersion +from fnmatch import fnmatch +import jinja2 + + +def get_dist_info_for(key): + try: + with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh: + info = json.load(fileh) + value = str(info[key]) + except (OSError, KeyError) as e: + print("BUILD FAILURE: Couldn't extract the key `" + key + "` " + + "from dist_info.json: " + str(e)) + sys.exit(1) + return value + + +def get_hostpython(): + return get_dist_info_for('hostpython') + + +def get_python_version(): + return get_dist_info_for('python_version') + + +def get_bootstrap_name(): + return get_dist_info_for('bootstrap') + + +if os.name == 'nt': + ANDROID = 'android.bat' + ANT = 'ant.bat' +else: + ANDROID = 'android' + ANT = 'ant' + +curdir = dirname(__file__) + +PYTHON = get_hostpython() +PYTHON_VERSION = get_python_version() +if PYTHON is not None and not exists(PYTHON): + PYTHON = None + +BLACKLIST_PATTERNS = [ + # code versionning + '^*.hg/*', + '^*.git/*', + '^*.bzr/*', + '^*.svn/*', + + # temp files + '~', + '*.bak', + '*.swp', +] +# pyc/py +if PYTHON is not None: + BLACKLIST_PATTERNS.append('*.py') + if PYTHON_VERSION and int(PYTHON_VERSION[0]) == 2: + # we only blacklist `.pyc` for python2 because in python3 the compiled + # extension is `.pyc` (.pyo files not exists for python >= 3.6) + BLACKLIST_PATTERNS.append('*.pyc') + +WHITELIST_PATTERNS = [] +if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'): + WHITELIST_PATTERNS.append('pyconfig.h') + +python_files = [] + + +environment = jinja2.Environment(loader=jinja2.FileSystemLoader( + join(curdir, 'templates'))) + + +def try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + +def render(template, dest, **kwargs): + '''Using jinja2, render `template` to the filename `dest`, supplying the + + keyword arguments as template parameters. + ''' + + dest_dir = dirname(dest) + if dest_dir and not exists(dest_dir): + makedirs(dest_dir) + + template = environment.get_template(template) + text = template.render(**kwargs) + + f = open(dest, 'wb') + f.write(text.encode('utf-8')) + f.close() + + +def is_whitelist(name): + return match_filename(WHITELIST_PATTERNS, name) + + +def is_blacklist(name): + if is_whitelist(name): + return False + return match_filename(BLACKLIST_PATTERNS, name) + + +def match_filename(pattern_list, name): + for pattern in pattern_list: + if pattern.startswith('^'): + pattern = pattern[1:] + else: + pattern = '*/' + pattern + if fnmatch(name, pattern): + return True + + +def listfiles(d): + basedir = d + subdirlist = [] + for item in os.listdir(d): + fn = join(d, item) + if isfile(fn): + yield fn + else: + subdirlist.append(join(basedir, item)) + for subdir in subdirlist: + for fn in listfiles(subdir): + yield fn + + +def make_python_zip(): + ''' + Search for all the python related files, and construct the pythonXX.zip + According to + # http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html + site-packages, config and lib-dynload will be not included. + ''' + + if not exists('private'): + print('No compiled python is present to zip, skipping.') + return + + global python_files + d = realpath(join('private', 'lib', 'python2.7')) + + def select(fn): + if is_blacklist(fn): + return False + fn = realpath(fn) + assert(fn.startswith(d)) + fn = fn[len(d):] + if (fn.startswith('/site-packages/') + or fn.startswith('/config/') + or fn.startswith('/lib-dynload/') + or fn.startswith('/libpymodules.so')): + return False + return fn + + # get a list of all python file + python_files = [x for x in listfiles(d) if select(x)] + + # create the final zipfile + zfn = join('private', 'lib', 'python27.zip') + zf = ZipFile(zfn, 'w') + + # put all the python files in it + for fn in python_files: + afn = fn[len(d):] + zf.write(fn, afn) + zf.close() + + +def make_tar(tfn, source_dirs, ignore_path=[], optimize_python=True): + ''' + Make a zip file `fn` from the contents of source_dis. + ''' + + # selector function + def select(fn): + rfn = realpath(fn) + for p in ignore_path: + if p.endswith('/'): + p = p[:-1] + if rfn.startswith(p): + return False + if rfn in python_files: + return False + return not is_blacklist(fn) + + # get the files and relpath file of all the directory we asked for + files = [] + for sd in source_dirs: + sd = realpath(sd) + compile_dir(sd, optimize_python=optimize_python) + files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd) + if select(x)] + + # create tar.gz of thoses files + tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT) + dirs = [] + for fn, afn in files: + dn = dirname(afn) + if dn not in dirs: + # create every dirs first if not exist yet + d = '' + for component in split(dn): + d = join(d, component) + if d.startswith('/'): + d = d[1:] + if d == '' or d in dirs: + continue + dirs.append(d) + tinfo = tarfile.TarInfo(d) + tinfo.type = tarfile.DIRTYPE + tf.addfile(tinfo) + + # put the file + tf.add(fn, afn) + tf.close() + + +def compile_dir(dfn, optimize_python=True): + ''' + Compile *.py in directory `dfn` to *.pyo + ''' + + if PYTHON is None: + return + + if int(PYTHON_VERSION[0]) >= 3: + args = [PYTHON, '-m', 'compileall', '-b', '-f', dfn] + else: + args = [PYTHON, '-m', 'compileall', '-f', dfn] + if optimize_python: + # -OO = strip docstrings + args.insert(1, '-OO') + return_code = subprocess.call(args) + + if return_code != 0: + print('Error while running "{}"'.format(' '.join(args))) + print('This probably means one of your Python files has a syntax ' + 'error, see logs above') + exit(1) + + +def make_package(args): + # If no launcher is specified, require a main.py/main.pyo: + if (get_bootstrap_name() != "sdl" or args.launcher is None) and \ + get_bootstrap_name() != "webview": + # (webview doesn't need an entrypoint, apparently) + if args.private is None or ( + not exists(join(realpath(args.private), 'main.py')) and + not exists(join(realpath(args.private), 'main.pyo'))): + print('''BUILD FAILURE: No main.py(o) found in your app directory. This +file must exist to act as the entry point for you app. If your app is +started by a file with a different name, rename it to main.py or add a +main.py that loads it.''') + sys.exit(1) + + assets_dir = "src/main/assets" + + # Delete the old assets. + try_unlink(join(assets_dir, 'public.mp3')) + try_unlink(join(assets_dir, 'private.mp3')) + ensure_dir(assets_dir) + + # In order to speedup import and initial depack, + # construct a python27.zip + make_python_zip() + + # Add extra environment variable file into tar-able directory: + env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-") + with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f: + f.write("P4A_IS_WINDOWED=" + str(args.window) + "\n") + if hasattr(args, "orientation"): + f.write("P4A_ORIENTATION=" + str(args.orientation) + "\n") + f.write("P4A_NUMERIC_VERSION=" + str(args.numeric_version) + "\n") + f.write("P4A_MINSDK=" + str(args.min_sdk_version) + "\n") + + # Package up the private data (public not supported). + tar_dirs = [env_vars_tarpath] + if args.private: + tar_dirs.append(args.private) + for python_bundle_dir in ('private', 'crystax_python', '_python_bundle'): + if exists(python_bundle_dir): + tar_dirs.append(python_bundle_dir) + if get_bootstrap_name() == "webview": + tar_dirs.append('webview_includes') + if args.private or args.launcher: + make_tar( + join(assets_dir, 'private.mp3'), tar_dirs, args.ignore_path, + optimize_python=args.optimize_python) + + # Remove extra env vars tar-able directory: + shutil.rmtree(env_vars_tarpath) + + # Prepare some variables for templating process + res_dir = "src/main/res" + default_icon = 'templates/kivy-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy( + args.icon or default_icon, + join(res_dir, 'drawable/icon.png') + ) + if get_bootstrap_name() != "service_only": + shutil.copy( + args.presplash or default_presplash, + join(res_dir, 'drawable/presplash.jpg') + ) + + # If extra Java jars were requested, copy them into the libs directory + jars = [] + if args.add_jar: + for jarname in args.add_jar: + if not exists(jarname): + print('Requested jar does not exist: {}'.format(jarname)) + sys.exit(-1) + shutil.copy(jarname, 'src/main/libs') + jars.append(basename(jarname)) + + # If extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) + + versioned_name = (args.name.replace(' ', '').replace('\'', '') + + '-' + args.version) + + version_code = 0 + if not args.numeric_version: + # Set version code in format (arch-minsdk-app_version) + with open(join(dirname(__file__), 'dist_info.json'), 'r') as dist_info: + dist_data = json.load(dist_info) + arch = dist_data["archs"][0] + arch_dict = {"x86_64": "9", "arm64-v8a": "8", "armeabi-v7a": "7", "x86": "6"} + arch_code = arch_dict.get(arch, '1') + min_sdk = args.min_sdk_version + for i in args.version.split('.'): + version_code *= 100 + version_code += int(i) + args.numeric_version = "{}{}{}".format(arch_code, min_sdk, version_code) + + if args.intent_filters: + with open(args.intent_filters) as fd: + args.intent_filters = fd.read() + + if not args.add_activity: + args.add_activity = [] + + if not args.activity_launch_mode: + args.activity_launch_mode = '' + + if args.extra_source_dirs: + esd = [] + for spec in args.extra_source_dirs: + if ':' in spec: + specdir, specincludes = spec.split(':') + else: + specdir = spec + specincludes = '**' + esd.append((realpath(specdir), specincludes)) + args.extra_source_dirs = esd + else: + args.extra_source_dirs = [] + + service = False + if args.private: + service_main = join(realpath(args.private), 'service', 'main.py') + if exists(service_main) or exists(service_main + 'o'): + service = True + + service_names = [] + for sid, spec in enumerate(args.services): + spec = spec.split(':') + name = spec[0] + entrypoint = spec[1] + options = spec[2:] + + foreground = 'foreground' in options + sticky = 'sticky' in options + + service_names.append(name) + service_target_path =\ + 'src/main/java/{}/Service{}.java'.format( + args.package.replace(".", "/"), + name.capitalize() + ) + render( + 'Service.tmpl.java', + service_target_path, + name=name, + entrypoint=entrypoint, + args=args, + foreground=foreground, + sticky=sticky, + service_id=sid + 1, + ) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + try: + int(android_api) + except (ValueError, TypeError): + raise ValueError( + "failed to extract the Android API level from " + + "build.properties. expected int, got: '" + + str(android_api) + "'" + ) + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + ignored = {".DS_Store", ".ds_store"} + build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored] + build_tools_versions = sorted(build_tools_versions, + key=LooseVersion) + build_tools_version = build_tools_versions[-1] + + # Folder name for launcher (used by SDL2 bootstrap) + url_scheme = 'kivy' + + # Render out android manifest: + manifest_path = "src/main/AndroidManifest.xml" + render_args = { + "args": args, + "service": service, + "service_names": service_names, + "android_api": android_api + } + if get_bootstrap_name() == "sdl2": + render_args["url_scheme"] = url_scheme + render( + 'AndroidManifest.tmpl.xml', + manifest_path, + **render_args) + + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(manifest_path, 'AndroidManifest.xml') + + # gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + jars=jars, + android_api=android_api, + build_tools_version=build_tools_version + ) + + # ant build templates + render( + 'build.tmpl.xml', + 'build.xml', + args=args, + versioned_name=versioned_name) + + # String resources: + render_args = { + "args": args, + "private_version": str(time.time()) + } + if get_bootstrap_name() == "sdl2": + render_args["url_scheme"] = url_scheme + render( + 'strings.tmpl.xml', + join(res_dir, 'values/strings.xml'), + **render_args) + + if exists(join("templates", "custom_rules.tmpl.xml")): + render( + 'custom_rules.tmpl.xml', + 'custom_rules.xml', + args=args) + + if get_bootstrap_name() == "webview": + render('WebViewLoader.tmpl.java', + 'src/main/java/org/kivy/android/WebViewLoader.java', + args=args) + + if args.sign: + render('build.properties', 'build.properties') + else: + if exists('build.properties'): + os.remove('build.properties') + + # Apply java source patches if any are present: + if exists(join('src', 'patches')): + print("Applying Java source code patches...") + for patch_name in os.listdir(join('src', 'patches')): + patch_path = join('src', 'patches', patch_name) + print("Applying patch: " + str(patch_path)) + try: + subprocess.check_output([ + # -N: insist this is FORWARd patch, don't reverse apply + # -p1: strip first path component + # -t: batch mode, don't ask questions + "patch", "-N", "-p1", "-t", "-i", patch_path + ]) + except subprocess.CalledProcessError as e: + if e.returncode == 1: + # Return code 1 means it didn't apply, this will + # usually mean it is already applied. + print("Warning: failed to apply patch (" + + "exit code 1), " + + "assuming it is already applied: " + + str(patch_path) + ) + else: + raise e + + +def parse_args(args=None): + global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON + + # Get the default minsdk, equal to the NDK API that this dist is built against + try: + with open('dist_info.json', 'r') as fileh: + info = json.load(fileh) + default_min_api = int(info['ndk_api']) + ndk_api = default_min_api + except (OSError, KeyError, ValueError, TypeError): + print('WARNING: Failed to read ndk_api from dist info, defaulting to 12') + default_min_api = 12 # The old default before ndk_api was introduced + ndk_api = 12 + + import argparse + ap = argparse.ArgumentParser(description='''\ +Package a Python application for Android (using +bootstrap ''' + get_bootstrap_name() + '''). + +For this to work, Java and Ant need to be in your path, as does the +tools directory of the Android SDK. +''') + + # --private is required unless for sdl2, where there's also --launcher + ap.add_argument('--private', dest='private', + help='the directory with the app source code files' + + ' (containing your main.py entrypoint)', + required=(get_bootstrap_name() != "sdl2")) + ap.add_argument('--package', dest='package', + help=('The name of the java package the project will be' + ' packaged under.'), + required=True) + ap.add_argument('--name', dest='name', + help=('The human-readable name of the project.'), + required=True) + ap.add_argument('--numeric-version', dest='numeric_version', + help=('The numeric version number of the project. If not ' + 'given, this is automatically computed from the ' + 'version.')) + ap.add_argument('--version', dest='version', + help=('The version number of the project. This should ' + 'consist of numbers and dots, and should have the ' + 'same number of groups of numbers as previous ' + 'versions.'), + required=True) + if get_bootstrap_name() == "sdl2": + ap.add_argument('--launcher', dest='launcher', action='store_true', + help=('Provide this argument to build a multi-app ' + 'launcher, rather than a single app.')) + ap.add_argument('--permission', dest='permissions', action='append', default=[], + help='The permissions to give this app.', nargs='+') + ap.add_argument('--meta-data', dest='meta_data', action='append', default=[], + help='Custom key=value to add in application metadata') + ap.add_argument('--uses-library', dest='android_used_libs', action='append', default=[], + help='Used shared libraries included using tag in AndroidManifest.xml') + ap.add_argument('--icon', dest='icon', + help=('A png file to use as the icon for ' + 'the application.')) + ap.add_argument('--service', dest='services', action='append', default=[], + help='Declare a new service entrypoint: ' + 'NAME:PATH_TO_PY[:foreground]') + if get_bootstrap_name() != "service_only": + ap.add_argument('--presplash', dest='presplash', + help=('A jpeg file to use as a screen while the ' + 'application is loading.')) + ap.add_argument('--presplash-color', + dest='presplash_color', + default='#000000', + help=('A string to set the loading screen ' + 'background color. ' + 'Supported formats are: ' + '#RRGGBB #AARRGGBB or color names ' + 'like red, green, blue, etc.')) + ap.add_argument('--window', dest='window', action='store_true', + default=False, + help='Indicate if the application will be windowed') + ap.add_argument('--orientation', dest='orientation', + default='portrait', + help=('The orientation that the game will ' + 'display in. ' + 'Usually one of "landscape", "portrait", ' + '"sensor", or "user" (the same as "sensor" ' + 'but obeying the ' + 'user\'s Android rotation setting). ' + 'The full list of options is given under ' + 'android_screenOrientation at ' + 'https://developer.android.com/guide/' + 'topics/manifest/' + 'activity-element.html')) + ap.add_argument('--wakelock', dest='wakelock', action='store_true', + help=('Indicate if the application needs the device ' + 'to stay on')) + ap.add_argument('--blacklist', dest='blacklist', + default=join(curdir, 'blacklist.txt'), + help=('Use a blacklist file to match unwanted file in ' + 'the final APK')) + ap.add_argument('--whitelist', dest='whitelist', + default=join(curdir, 'whitelist.txt'), + help=('Use a whitelist file to prevent blacklisting of ' + 'file in the final APK')) + ap.add_argument('--add-jar', dest='add_jar', action='append', + help=('Add a Java .jar to the libs, so you can access its ' + 'classes with pyjnius. You can specify this ' + 'argument more than once to include multiple jars')) + ap.add_argument('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + # The --sdk option has been removed, it is ignored in favour of + # --android-api handled by toolchain.py + ap.add_argument('--sdk', dest='sdk_version', default=-1, + type=int, help=('Deprecated argument, does nothing')) + ap.add_argument('--minsdk', dest='min_sdk_version', + default=default_min_api, type=int, + help=('Minimum Android SDK version that the app supports. ' + 'Defaults to {}.'.format(default_min_api))) + ap.add_argument('--allow-minsdk-ndkapi-mismatch', default=False, + action='store_true', + help=('Allow the --minsdk argument to be different from ' + 'the discovered ndk_api in the dist')) + ap.add_argument('--intent-filters', dest='intent_filters', + help=('Add intent-filters xml rules to the ' + 'AndroidManifest.xml file. The argument is a ' + 'filename containing xml. The filename should be ' + 'located relative to the python-for-android ' + 'directory')) + ap.add_argument('--with-billing', dest='billing_pubkey', + help='If set, the billing service will be added (not implemented)') + ap.add_argument('--add-source', dest='extra_source_dirs', action='append', + help='Include additional source dirs in Java build') + if get_bootstrap_name() == "webview": + ap.add_argument('--port', + help='The port on localhost that the WebView will access', + default='5000') + ap.add_argument('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') + ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', + help='Do not optimise .py files to .pyo.') + ap.add_argument('--sign', action='store_true', + help=('Try to sign the APK with your credentials. You must set ' + 'the appropriate environment variables.')) + ap.add_argument('--add-activity', dest='add_activity', action='append', + help='Add this Java class as an Activity to the manifest.') + ap.add_argument('--activity-launch-mode', + dest='activity_launch_mode', + default='singleTask', + help='Set the launch mode of the main activity in the manifest.') + ap.add_argument('--allow-backup', dest='allow_backup', default='true', + help="if set to 'false', then android won't backup the application.") + ap.add_argument('--no-optimize-python', dest='optimize_python', + action='store_false', default=True, + help=('Whether to compile to optimised .pyo files, using -OO ' + '(strips docstrings and asserts)')) + + # Put together arguments, and add those from .p4a config file: + if args is None: + args = sys.argv[1:] + + def _read_configuration(): + if not exists(".p4a"): + return + print("Reading .p4a configuration") + with open(".p4a") as fd: + lines = fd.readlines() + lines = [shlex.split(line) + for line in lines if not line.startswith("#")] + for line in lines: + for arg in line: + args.append(arg) + _read_configuration() + + args = ap.parse_args(args) + args.ignore_path = [] + + if args.name and args.name[0] == '"' and args.name[-1] == '"': + args.name = args.name[1:-1] + + if ndk_api != args.min_sdk_version: + print(('WARNING: --minsdk argument does not match the api that is ' + 'compiled against. Only proceed if you know what you are ' + 'doing, otherwise use --minsdk={} or recompile against api ' + '{}').format(ndk_api, args.min_sdk_version)) + if not args.allow_minsdk_ndkapi_mismatch: + print('You must pass --allow-minsdk-ndkapi-mismatch to build ' + 'with --minsdk different to the target NDK api from the ' + 'build step') + sys.exit(1) + else: + print('Proceeding with --minsdk not matching build target api') + + if args.billing_pubkey: + print('Billing not yet supported!') + sys.exit(1) + + if args.sdk_version == -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') + args.sdk_version = -1 # ensure it is not used + + if args.permissions and isinstance(args.permissions[0], list): + args.permissions = [p for perm in args.permissions for p in perm] + + if args.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + + if args.no_compile_pyo: + PYTHON = None + BLACKLIST_PATTERNS.remove('*.py') + + if args.blacklist: + with open(args.blacklist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + BLACKLIST_PATTERNS += patterns + + if args.whitelist: + with open(args.whitelist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + WHITELIST_PATTERNS += patterns + + if args.private is None and \ + get_bootstrap_name() == 'sdl2' and args.launcher is None: + print('Need --private directory or ' + + '--launcher (SDL2 bootstrap only)' + + 'to have something to launch inside the .apk!') + sys.exit(1) + make_package(args) + + return args + + +if __name__ == "__main__": + parse_args() diff --git a/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..3d0dee6e Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar differ diff --git a/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..efc019a5 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/p4a/pythonforandroid/bootstraps/common/build/gradlew b/p4a/pythonforandroid/bootstraps/common/build/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/p4a/pythonforandroid/bootstraps/common/build/gradlew.bat b/p4a/pythonforandroid/bootstraps/common/build/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p4a/pythonforandroid/bootstraps/common/build/jni/Android.mk b/p4a/pythonforandroid/bootstraps/common/build/jni/Android.mk new file mode 100644 index 00000000..5053e7d6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/jni/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/p4a/pythonforandroid/bootstraps/common/build/jni/application/Android.mk b/p4a/pythonforandroid/bootstraps/common/build/jni/application/Android.mk new file mode 100644 index 00000000..5053e7d6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/jni/application/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk new file mode 100644 index 00000000..4a442eeb --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +SDL_PATH := ../../SDL + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include + +# Add your application source files here... +LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ + start.c + +LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) + +LOCAL_SHARED_LIBRARIES := SDL2 python_shared + +LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS) + +LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS) + +include $(BUILD_SHARED_LIBRARY) + +ifdef CRYSTAX_PYTHON_VERSION + $(call import-module,python/$(CRYSTAX_PYTHON_VERSION)) +endif diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/start.c b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c similarity index 52% rename from p4a/pythonforandroid/bootstraps/lbry/build/jni/src/start.c rename to p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c index 80cd5ace..34291181 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/start.c +++ b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c @@ -1,5 +1,4 @@ - #define PY_SSIZE_T_CLEAN #include "Python.h" #ifndef Py_PYTHON_H @@ -15,6 +14,16 @@ #include #include +#include "bootstrap_name.h" +#ifndef BOOTSTRAP_USES_NO_SDL_HEADERS +#include "SDL.h" +#ifndef BOOTSTRAP_NAME_PYGAME +#include "SDL_opengles2.h" +#endif +#endif +#ifdef BOOTSTRAP_NAME_PYGAME +#include "jniwrapperstuff.h" +#endif #include "android/log.h" #define ENTRYPOINT_MAXLEN 128 @@ -58,7 +67,7 @@ int dir_exists(char *filename) { int file_exists(const char *filename) { FILE *file; - if (file = fopen(filename, "r")) { + if ((file = fopen(filename, "r"))) { fclose(file); return 1; } @@ -75,25 +84,79 @@ int main(int argc, char *argv[]) { int ret = 0; FILE *fd; - /* AND: Several filepaths are hardcoded here, these must be made - configurable */ - /* AND: P4A uses env vars...not sure what's best */ - LOGP("Initialize Python for Android"); + LOGP("Initializing Python for Android"); + + // Set a couple of built-in environment vars: + setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications env_argument = getenv("ANDROID_ARGUMENT"); setenv("ANDROID_APP_PATH", env_argument, 1); env_entrypoint = getenv("ANDROID_ENTRYPOINT"); env_logname = getenv("PYTHON_NAME"); - + if (!getenv("ANDROID_UNPACK")) { + /* ANDROID_UNPACK currently isn't set in services */ + setenv("ANDROID_UNPACK", env_argument, 1); + } if (env_logname == NULL) { env_logname = "python"; setenv("PYTHON_NAME", "python", 1); } + // Set additional file-provided environment vars: + LOGP("Setting additional env vars from p4a_env_vars.txt"); + char env_file_path[256]; + snprintf(env_file_path, sizeof(env_file_path), + "%s/p4a_env_vars.txt", getenv("ANDROID_UNPACK")); + FILE *env_file_fd = fopen(env_file_path, "r"); + if (env_file_fd) { + char* line = NULL; + size_t len = 0; + while (getline(&line, &len, env_file_fd) != -1) { + if (strlen(line) > 0) { + char *eqsubstr = strstr(line, "="); + if (eqsubstr) { + size_t eq_pos = eqsubstr - line; + + // Extract name: + char env_name[256]; + strncpy(env_name, line, sizeof(env_name)); + env_name[eq_pos] = '\0'; + + // Extract value (with line break removed: + char env_value[256]; + strncpy(env_value, (char*)(line + eq_pos + 1), sizeof(env_value)); + if (strlen(env_value) > 0 && + env_value[strlen(env_value)-1] == '\n') { + env_value[strlen(env_value)-1] = '\0'; + if (strlen(env_value) > 0 && + env_value[strlen(env_value)-1] == '\r') { + // Also remove windows line breaks (\r\n) + env_value[strlen(env_value)-1] = '\0'; + } + } + + // Set value: + setenv(env_name, env_value, 1); + } + } + } + fclose(env_file_fd); + } else { + LOGP("Warning: no p4a_env_vars.txt found / failed to open!"); + } + LOGP("Changing directory to the one provided by ANDROID_ARGUMENT"); LOGP(env_argument); chdir(env_argument); +#if PY_MAJOR_VERSION < 3 + Py_NoSiteFlag=1; +#endif + +#if PY_MAJOR_VERSION < 3 + Py_SetProgramName("android_python"); +#else Py_SetProgramName(L"android_python"); +#endif #if PY_MAJOR_VERSION >= 3 /* our logging module for android @@ -103,34 +166,55 @@ int main(int argc, char *argv[]) { LOGP("Preparing to initialize python"); - if (dir_exists("crystax_python/")) { - LOGP("crystax_python exists"); - char paths[256]; - snprintf(paths, 256, - "%s/crystax_python/stdlib.zip:%s/crystax_python/modules", - env_argument, env_argument); - /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument, - * env_argument); */ + // Set up the python path + char paths[256]; + + char crystax_python_dir[256]; + snprintf(crystax_python_dir, 256, + "%s/crystax_python", getenv("ANDROID_UNPACK")); + char python_bundle_dir[256]; + snprintf(python_bundle_dir, 256, + "%s/_python_bundle", getenv("ANDROID_UNPACK")); + if (dir_exists(crystax_python_dir) || dir_exists(python_bundle_dir)) { + if (dir_exists(crystax_python_dir)) { + LOGP("crystax_python exists"); + snprintf(paths, 256, + "%s/stdlib.zip:%s/modules", + crystax_python_dir, crystax_python_dir); + } + + if (dir_exists(python_bundle_dir)) { + LOGP("_python_bundle dir exists"); + snprintf(paths, 256, + "%s/stdlib.zip:%s/modules", + python_bundle_dir, python_bundle_dir); + } + LOGP("calculated paths to be..."); LOGP(paths); -#if PY_MAJOR_VERSION >= 3 - wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL); - Py_SetPath(wchar_paths); -#else - char *wchar_paths = paths; - LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet"); - exit(1); -#endif + #if PY_MAJOR_VERSION >= 3 + wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL); + Py_SetPath(wchar_paths); + #endif - LOGP("set wchar paths..."); + LOGP("set wchar paths..."); } else { - LOGP("crystax_python does not exist"); + // We do not expect to see crystax_python any more, so no point + // reminding the user about it. If it does exist, we'll have + // logged it earlier. + LOGP("_python_bundle does not exist"); } Py_Initialize(); #if PY_MAJOR_VERSION < 3 + // Can't Py_SetPath in python2 but we can set PySys_SetPath, which must + // be applied after Py_Initialize rather than before like Py_SetPath + #if PY_MICRO_VERSION >= 15 + // Only for python native-build + PySys_SetPath(paths); + #endif PySys_SetArgv(argc, argv); #endif @@ -153,8 +237,10 @@ int main(int argc, char *argv[]) { */ PyRun_SimpleString("import sys, posix\n"); if (dir_exists("lib")) { - /* If we built our own python, set up the paths correctly */ - LOGP("Setting up python from ANDROID_PRIVATE"); + /* If we built our own python, set up the paths correctly. + * This is only the case if we are using the python2legacy recipe + */ + LOGP("Setting up python from ANDROID_APP_PATH"); PyRun_SimpleString("private = posix.environ['ANDROID_APP_PATH']\n" "argument = posix.environ['ANDROID_ARGUMENT']\n" "sys.path[:] = [ \n" @@ -165,11 +251,24 @@ int main(int argc, char *argv[]) { " argument ]\n"); } - if (dir_exists("crystax_python")) { - char add_site_packages_dir[256]; + char add_site_packages_dir[256]; + if (dir_exists(crystax_python_dir)) { snprintf(add_site_packages_dir, 256, - "sys.path.append('%s/crystax_python/site-packages')", - env_argument); + "sys.path.append('%s/site-packages')", + crystax_python_dir); + + PyRun_SimpleString("import sys\n" + "sys.argv = ['notaninterpreterreally']\n" + "from os.path import realpath, join, dirname"); + PyRun_SimpleString(add_site_packages_dir); + /* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */ + PyRun_SimpleString("sys.path = ['.'] + sys.path"); + } + + if (dir_exists(python_bundle_dir)) { + snprintf(add_site_packages_dir, 256, + "sys.path.append('%s/site-packages')", + python_bundle_dir); PyRun_SimpleString("import sys\n" "sys.argv = ['notaninterpreterreally']\n" @@ -210,6 +309,11 @@ int main(int argc, char *argv[]) { /* Get the entrypoint, search the .pyo then .py */ char *dot = strrchr(env_entrypoint, '.'); +#if PY_MAJOR_VERSION > 2 + char *ext = ".pyc"; +#else + char *ext = ".pyo"; +#endif if (dot <= 0) { LOGP("Invalid entrypoint, abort."); return -1; @@ -218,14 +322,14 @@ int main(int argc, char *argv[]) { LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN."); return -1; } - if (!strcmp(dot, ".pyo")) { + if (!strcmp(dot, ext)) { if (!file_exists(env_entrypoint)) { /* fallback on .py */ strcpy(entrypoint, env_entrypoint); entrypoint[strlen(env_entrypoint) - 1] = '\0'; LOGP(entrypoint); if (!file_exists(entrypoint)) { - LOGP("Entrypoint not found (.pyo, fallback on .py), abort"); + LOGP("Entrypoint not found (.pyc/.pyo, fallback on .py), abort"); return -1; } } else { @@ -235,7 +339,11 @@ int main(int argc, char *argv[]) { /* if .py is passed, check the pyo version first */ strcpy(entrypoint, env_entrypoint); entrypoint[strlen(env_entrypoint) + 1] = '\0'; +#if PY_MAJOR_VERSION > 2 + entrypoint[strlen(env_entrypoint)] = 'c'; +#else entrypoint[strlen(env_entrypoint)] = 'o'; +#endif if (!file_exists(entrypoint)) { /* fallback on pure python version */ if (!file_exists(env_entrypoint)) { @@ -245,7 +353,7 @@ int main(int argc, char *argv[]) { strcpy(entrypoint, env_entrypoint); } } else { - LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort."); + LOGP("Entrypoint have an invalid extension (must be .py or .pyc/.pyo), abort."); return -1; } // LOGP("Entrypoint is:"); @@ -260,6 +368,7 @@ int main(int argc, char *argv[]) { /* run python ! */ ret = PyRun_SimpleFile(fd, entrypoint); + fclose(fd); if (PyErr_Occurred() != NULL) { ret = 1; @@ -270,19 +379,48 @@ int main(int argc, char *argv[]) { PyErr_Clear(); } - /* close everything - */ - Py_Finalize(); - fclose(fd); - LOGP("Python for android ended."); + + /* Shut down: since regular shutdown causes issues sometimes + (seems to be an incomplete shutdown breaking next launch) + we'll use sys.exit(ret) to shutdown, since that one works. + + Reference discussion: + + https://github.com/kivy/kivy/pull/6107#issue-246120816 + */ + char terminatecmd[256]; + snprintf( + terminatecmd, sizeof(terminatecmd), + "import sys; sys.exit(%d)\n", ret + ); + PyRun_SimpleString(terminatecmd); + + /* This should never actually be reached, but we'll leave the clean-up + * here just to be safe. + */ +#if PY_MAJOR_VERSION < 3 + Py_Finalize(); + LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful."); +#else + if (Py_FinalizeEx() != 0) // properly check success on Python 3 + LOGP("Unexpectedly reached Py_FinalizeEx(), and got error!"); + else + LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful."); +#endif + return ret; } JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart( - JNIEnv *env, jobject thiz, jstring j_android_private, - jstring j_android_argument, jstring j_service_entrypoint, - jstring j_python_name, jstring j_python_home, jstring j_python_path, + JNIEnv *env, + jobject thiz, + jstring j_android_private, + jstring j_android_argument, + jstring j_service_entrypoint, + jstring j_python_name, + jstring j_python_home, + jstring j_python_path, jstring j_arg) { jboolean iscopy; const char *android_private = @@ -308,10 +446,7 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart( setenv("PYTHONHOME", python_home, 1); setenv("PYTHONPATH", python_path, 1); setenv("PYTHON_SERVICE_ARGUMENT", arg, 1); - - char ca_path[128]; - snprintf(ca_path, 128, "%s/crystax_python/site-packages/certifi/cacert.pem", python_home); - setenv("SSL_CERT_FILE", ca_path, 1); + setenv("P4A_BOOTSTRAP", bootstrap_name, 1); char *argv[] = {"."}; /* ANDROID_ARGUMENT points to service subdir, @@ -320,4 +455,47 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart( main(1, argv); } +#if defined(BOOTSTRAP_NAME_WEBVIEW) || defined(BOOTSTRAP_NAME_SERVICEONLY) +// Webview and service_only uses some more functions: + +void Java_org_kivy_android_PythonActivity_nativeSetenv( + JNIEnv* env, jclass cls, + jstring name, jstring value) +//JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( +// JNIEnv* env, jclass cls, +// jstring name, jstring value) +{ + const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); + const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL); + + setenv(utfname, utfvalue, 1); + + (*env)->ReleaseStringUTFChars(env, name, utfname); + (*env)->ReleaseStringUTFChars(env, value, utfvalue); +} + + +void Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) +{ + /* This nativeInit follows SDL2 */ + + /* This interface could expand with ABI negotiation, calbacks, etc. */ + /* SDL_Android_Init(env, cls); */ + + /* SDL_SetMainReady(); */ + + /* Run the application code! */ + int status; + char *argv[2]; + argv[0] = "Python_app"; + argv[1] = NULL; + /* status = SDL_main(1, argv); */ + + main(1, argv); + + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ +} +#endif + #endif diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java new file mode 100644 index 00000000..4f20fb78 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java @@ -0,0 +1,164 @@ +package org.kivy.android; + +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import android.app.Service; +import android.os.IBinder; +import android.os.Bundle; +import android.content.Intent; +import android.content.Context; +import android.util.Log; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Process; +import java.io.File; + +import org.kivy.android.PythonUtil; + +import org.renpy.android.Hardware; + + +public class PythonService extends Service implements Runnable { + + // Thread for Python code + private Thread pythonThread = null; + + // Python environment variables + private String androidPrivate; + private String androidArgument; + private String pythonName; + private String pythonHome; + private String pythonPath; + private String serviceEntrypoint; + // Argument to pass to Python code, + private String pythonServiceArgument; + public static PythonService mService = null; + private Intent startIntent = null; + + private boolean autoRestartService = false; + + public void setAutoRestartService(boolean restart) { + autoRestartService = restart; + } + + public boolean canDisplayNotification() { + return true; + } + + public int startType() { + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (pythonThread != null) { + Log.v("python service", "service exists, do not start again"); + return START_NOT_STICKY; + } + + startIntent = intent; + Bundle extras = intent.getExtras(); + androidPrivate = extras.getString("androidPrivate"); + androidArgument = extras.getString("androidArgument"); + serviceEntrypoint = extras.getString("serviceEntrypoint"); + pythonName = extras.getString("pythonName"); + pythonHome = extras.getString("pythonHome"); + pythonPath = extras.getString("pythonPath"); + pythonServiceArgument = extras.getString("pythonServiceArgument"); + + pythonThread = new Thread(this); + pythonThread.start(); + + if (canDisplayNotification()) { + doStartForeground(extras); + } + + return startType(); + } + + protected void doStartForeground(Bundle extras) { + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + Notification notification; + Context context = getApplicationContext(); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(serviceTitle); + builder.setContentText(serviceDescription); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } + startForeground(1, notification); + } + + @Override + public void onDestroy() { + super.onDestroy(); + pythonThread = null; + if (autoRestartService && startIntent != null) { + Log.v("python service", "service restart requested"); + startService(startIntent); + } + Process.killProcess(Process.myPid()); + } + + /** + * Stops the task gracefully when killed. + * Calling stopSelf() will trigger a onDestroy() call from the system. + */ + @Override + public void onTaskRemoved(Intent rootIntent) { + super.onTaskRemoved(rootIntent); + stopSelf(); + } + + @Override + public void run(){ + String app_root = getFilesDir().getAbsolutePath() + "/app"; + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file, + new File(getApplicationInfo().nativeLibraryDir)); + this.mService = this; + nativeStart( + androidPrivate, androidArgument, + serviceEntrypoint, pythonName, + pythonHome, pythonPath, + pythonServiceArgument); + stopSelf(); + } + + // Native part + public static native void nativeStart( + String androidPrivate, String androidArgument, + String serviceEntrypoint, String pythonName, + String pythonHome, String pythonPath, + String pythonServiceArgument); +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java new file mode 100644 index 00000000..1f267385 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java @@ -0,0 +1,77 @@ +package org.kivy.android; + +import java.io.File; + +import android.util.Log; +import java.util.ArrayList; +import java.io.FilenameFilter; +import java.util.regex.Pattern; + + +public class PythonUtil { + private static final String TAG = "pythonutil"; + + protected static void addLibraryIfExists(ArrayList libsList, String pattern, File libsDir) { + // pattern should be the name of the lib file, without the + // preceding "lib" or suffix ".so", for instance "ssl.*" will + // match files of the form "libssl.*.so". + File [] files = libsDir.listFiles(); + + pattern = "lib" + pattern + "\\.so"; + Pattern p = Pattern.compile(pattern); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + String name = file.getName(); + Log.v(TAG, "Checking pattern " + pattern + " against " + name); + if (p.matcher(name).matches()) { + Log.v(TAG, "Pattern " + pattern + " matched file " + name); + libsList.add(name.substring(3, name.length() - 3)); + } + } + } + + protected static ArrayList getLibraries(File libsDir) { + ArrayList libsList = new ArrayList(); + addLibraryIfExists(libsList, "crystax", libsDir); + addLibraryIfExists(libsList, "sqlite3", libsDir); + addLibraryIfExists(libsList, "ffi", libsDir); + addLibraryIfExists(libsList, "ssl.*", libsDir); + addLibraryIfExists(libsList, "crypto.*", libsDir); + libsList.add("python2.7"); + libsList.add("python3.5m"); + libsList.add("python3.6m"); + libsList.add("python3.7m"); + libsList.add("main"); + return libsList; + } + + public static void loadLibraries(File filesDir, File libsDir) { + String filesDirPath = filesDir.getAbsolutePath(); + boolean foundPython = false; + + for (String lib : getLibraries(libsDir)) { + Log.v(TAG, "Loading library: " + lib); + try { + System.loadLibrary(lib); + if (lib.startsWith("python")) { + foundPython = true; + } + } catch(UnsatisfiedLinkError e) { + // If this is the last possible libpython + // load, and it has failed, give a more + // general error + Log.v(TAG, "Library loading error: " + e.getMessage()); + if (lib.startsWith("python3.7") && !foundPython) { + throw new java.lang.RuntimeException("Could not load any libpythonXXX.so"); + } else if (lib.startsWith("python")) { + continue; + } else { + Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib); + throw e; + } + } + } + + Log.v(TAG, "Loaded everything!"); + } +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java new file mode 100644 index 00000000..52d6424e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java @@ -0,0 +1,115 @@ +// This string is autogenerated by ChangeAppSettings.sh, do not change +// spaces amount +package org.renpy.android; + +import java.io.*; + +import android.app.Activity; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.File; + +import java.util.zip.GZIPInputStream; + +import android.content.res.AssetManager; + +import org.kamranzafar.jtar.*; + +public class AssetExtract { + + private AssetManager mAssetManager = null; + private Activity mActivity = null; + + public AssetExtract(Activity act) { + mActivity = act; + mAssetManager = act.getAssets(); + } + + public boolean extractTar(String asset, String target) { + + byte buf[] = new byte[1024 * 1024]; + + InputStream assetStream = null; + TarInputStream tis = null; + + try { + assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING); + tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192)); + } catch (IOException e) { + Log.e("python", "opening up extract tar", e); + return false; + } + + while (true) { + TarEntry entry = null; + + try { + entry = tis.getNextEntry(); + } catch ( java.io.IOException e ) { + Log.e("python", "extracting tar", e); + return false; + } + + if ( entry == null ) { + break; + } + + Log.v("python", "extracting " + entry.getName()); + + if (entry.isDirectory()) { + + try { + new File(target +"/" + entry.getName()).mkdirs(); + } catch ( SecurityException e ) { }; + + continue; + } + + OutputStream out = null; + String path = target + "/" + entry.getName(); + + try { + out = new BufferedOutputStream(new FileOutputStream(path), 8192); + } catch ( FileNotFoundException e ) { + } catch ( SecurityException e ) { }; + + if ( out == null ) { + Log.e("python", "could not open " + path); + return false; + } + + try { + while (true) { + int len = tis.read(buf); + + if (len == -1) { + break; + } + + out.write(buf, 0, len); + } + + out.flush(); + out.close(); + } catch ( java.io.IOException e ) { + Log.e("python", "extracting zip", e); + return false; + } + } + + try { + tis.close(); + assetStream.close(); + } catch (IOException e) { + // pass + } + + return true; + } +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java new file mode 100644 index 00000000..47455abb --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java @@ -0,0 +1,54 @@ +/** + * This class takes care of managing resources for us. In our code, we + * can't use R, since the name of the package containing R will + * change. (This same code is used in both org.renpy.android and + * org.renpy.pygame.) So this is the next best thing. + */ + +package org.renpy.android; + +import android.app.Activity; +import android.content.res.Resources; +import android.view.View; + +import android.util.Log; + +public class ResourceManager { + + private Activity act; + private Resources res; + + public ResourceManager(Activity activity) { + act = activity; + res = act.getResources(); + } + + public int getIdentifier(String name, String kind) { + Log.v("SDL", "getting identifier"); + Log.v("SDL", "kind is " + kind + " and name " + name); + Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName())); + return res.getIdentifier(name, kind, act.getPackageName()); + } + + public String getString(String name) { + + try { + Log.v("SDL", "asked to get string " + name); + return res.getString(getIdentifier(name, "string")); + } catch (Exception e) { + Log.v("SDL", "got exception looking for string!"); + return null; + } + } + + public View inflateView(String name) { + int id = getIdentifier(name, "layout"); + return act.getLayoutInflater().inflate(id, null); + } + + public View getViewById(View v, String name) { + int id = getIdentifier(name, "id"); + return v.findViewById(id); + } + +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java new file mode 100644 index 00000000..3ed10c26 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java @@ -0,0 +1,77 @@ +package {{ args.package }}; + +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import android.content.Intent; +import android.content.Context; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Bundle; +import org.kivy.android.PythonService; +import org.kivy.android.PythonActivity; + + +public class Service{{ name|capitalize }} extends PythonService { + {% if sticky %} + @Override + public int startType() { + return START_STICKY; + } + {% endif %} + + {% if not foreground %} + @Override + public boolean canDisplayNotification() { + return false; + } + {% endif %} + + @Override + protected void doStartForeground(Bundle extras) { + Notification notification; + Context context = getApplicationContext(); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle("{{ args.name }}"); + builder.setContentText("{{ name| capitalize }}"); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } + startForeground({{ service_id }}, notification); + } + + static public void start(Context ctx, String pythonServiceArgument) { + Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); + String argument = ctx.getFilesDir().getAbsolutePath() + "/app"; + intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath()); + intent.putExtra("androidArgument", argument); + intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); + intent.putExtra("pythonName", "{{ name }}"); + intent.putExtra("pythonHome", argument); + intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); + intent.putExtra("pythonServiceArgument", pythonServiceArgument); + ctx.startService(intent); + } + + static public void stop(Context ctx) { + Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); + ctx.stopService(intent); + } +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/build.properties b/p4a/pythonforandroid/bootstraps/common/build/templates/build.properties new file mode 100644 index 00000000..f12e2586 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/templates/build.properties @@ -0,0 +1,21 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +key.store=${env.P4A_RELEASE_KEYSTORE} +key.alias=${env.P4A_RELEASE_KEYALIAS} +key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD} +key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD} diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle new file mode 100644 index 00000000..32bd091b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle @@ -0,0 +1,80 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.4' + } +} + +allprojects { + repositories { + google() + jcenter() + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion {{ android_api }} + buildToolsVersion '{{ build_tools_version }}' + defaultConfig { + minSdkVersion {{ args.min_sdk_version }} + targetSdkVersion {{ android_api }} + versionCode {{ args.numeric_version }} + versionName '{{ args.version }}' + } + + {% if args.sign -%} + signingConfigs { + release { + storeFile file(System.getenv("P4A_RELEASE_KEYSTORE")) + keyAlias System.getenv("P4A_RELEASE_KEYALIAS") + storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD") + keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD") + } + } + {%- endif %} + + buildTypes { + debug { + } + release { + {% if args.sign -%} + signingConfig signingConfigs.release + {%- endif %} + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + sourceSets { + main { + jniLibs.srcDir 'libs' + } + } + +} + +dependencies { + {%- for aar in aars %} + compile(name: '{{ aar }}', ext: 'aar') + {%- endfor -%} + {%- for jar in jars %} + compile files('src/main/libs/{{ jar }}') + {%- endfor -%} + {%- if args.depends -%} + {%- for depend in args.depends %} + compile '{{ depend }}' + {%- endfor %} + {%- endif %} +} diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml new file mode 100644 index 00000000..9ab301ad --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml b/p4a/pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml new file mode 100644 index 00000000..a6a7ebad --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml @@ -0,0 +1,21 @@ + + + + + {% if args.launcher %} + + {% else %} + + + + + {% endif %} + {% for dir, includes in args.extra_source_dirs %} + + {% endfor %} + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-icon.png b/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-icon.png new file mode 100644 index 00000000..6ecb013b Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-icon.png differ diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-presplash.jpg b/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-presplash.jpg new file mode 100644 index 00000000..c61efa27 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/common/build/templates/kivy-presplash.jpg differ diff --git a/p4a/pythonforandroid/bootstraps/common/build/whitelist.txt b/p4a/pythonforandroid/bootstraps/common/build/whitelist.txt new file mode 100644 index 00000000..41b06ee2 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/common/build/whitelist.txt @@ -0,0 +1 @@ +# put files here that you need to un-blacklist diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/Android.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk similarity index 56% rename from p4a/pythonforandroid/bootstraps/lbry/build/jni/src/Android.mk rename to p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk index 018a7cad..0bc42bfb 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/Android.mk +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk @@ -7,13 +7,13 @@ LOCAL_MODULE := main # Add your application source files here... LOCAL_SRC_FILES := start.c pyjniusjni.c -LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS) +LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) LOCAL_SHARED_LIBRARIES := python_shared LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS) -LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS) +LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS) include $(BUILD_SHARED_LIBRARY) diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/Android_static.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/jni/src/Android_static.mk rename to p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h new file mode 100644 index 00000000..b93a4ae6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h @@ -0,0 +1,6 @@ + +#define BOOTSTRAP_NAME_SERVICEONLY +#define BOOTSTRAP_USES_NO_SDL_HEADERS + +const char bootstrap_name[] = "service_only"; + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/src/pyjniusjni.c b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/jni/src/pyjniusjni.c rename to p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle index b6908b88..e869edc7 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle @@ -41,12 +41,6 @@ android { } } - applicationVariants.all { variant -> - variant.outputs.all { - outputFileName = "../" + outputFileName - } - } - dexOptions { jumboMode true } diff --git a/p4a/pythonforandroid/build.py b/p4a/pythonforandroid/build.py index d522531b..310cfc58 100644 --- a/p4a/pythonforandroid/build.py +++ b/p4a/pythonforandroid/build.py @@ -2,21 +2,25 @@ from __future__ import print_function from os.path import (join, realpath, dirname, expanduser, exists, split, isdir) -from os import environ, listdir +from os import environ +import copy import os import glob import sys import re import sh +import subprocess -from pythonforandroid.util import (ensure_dir, current_directory) -from pythonforandroid.logger import (info, warning, error, info_notify, - Err_Fore, Err_Style, info_main, - shprint) -from pythonforandroid.archs import ArchARM, ArchARMv7_a, Archx86, Archx86_64, ArchAarch_64 -from pythonforandroid.recipe import Recipe - -DEFAULT_ANDROID_API = 15 +from pythonforandroid.util import ( + current_directory, ensure_dir, get_virtualenv_executable, + BuildInterruptingException +) +from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint) +from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64 +from pythonforandroid.recipe import CythonRecipe, Recipe +from pythonforandroid.recommendations import ( + check_ndk_version, check_target_api, check_ndk_api, + RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API) class Context(object): @@ -24,14 +28,19 @@ class Context(object): will be instantiated and used to hold all the build state.''' env = environ.copy() - root_dir = None # the filepath of toolchain.py - storage_dir = None # the root dir where builds and dists will be stored + # the filepath of toolchain.py + root_dir = None + # the root dir where builds and dists will be stored + storage_dir = None - build_dir = None # in which bootstraps are copied for building - # and recipes are built - 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 + # in which bootstraps are copied for building + # and recipes are built + build_dir = None + # the Android project folder where everything ends up + dist_dir = None + # where Android libs are cached after build + # but before being placed in dists + libs_dir = None aars_dir = None ccache = None # whether to use ccache @@ -45,7 +54,7 @@ class Context(object): recipe_build_order = None # Will hold the list of all built recipes - symlink_java_src = False # If True, will symlink instead of copying during build + symlink_java_src = False # If True, will symlink instead of copying during build java_build_tool = 'auto' @@ -121,17 +130,17 @@ class Context(object): self._android_api = value @property - def ndk_ver(self): - '''The version of the NDK being used for compilation.''' - if self._ndk_ver is None: - raise ValueError('Tried to access ndk_ver but it has not ' + def ndk_api(self): + '''The API number compile against''' + if self._ndk_api is None: + raise ValueError('Tried to access ndk_api but it has not ' 'been set - this should not happen, something ' 'went wrong!') - return self._ndk_ver + return self._ndk_api - @ndk_ver.setter - def ndk_ver(self, value): - self._ndk_ver = value + @ndk_api.setter + def ndk_api(self, value): + self._ndk_api = value @property def sdk_dir(self): @@ -159,9 +168,11 @@ class Context(object): def ndk_dir(self, value): self._ndk_dir = value - def prepare_build_environment(self, user_sdk_dir, user_ndk_dir, - user_android_api, user_android_min_api, - user_ndk_ver): + def prepare_build_environment(self, + user_sdk_dir, + user_ndk_dir, + user_android_api, + user_ndk_api): '''Checks that build dependencies exist and sets internal variables for the Android SDK etc. @@ -180,12 +191,14 @@ class Context(object): sdk_dir = None if user_sdk_dir: sdk_dir = user_sdk_dir - if sdk_dir is None: # This is the old P4A-specific var + # This is the old P4A-specific var + if sdk_dir is None: sdk_dir = environ.get('ANDROIDSDK', None) - if sdk_dir is None: # This seems used more conventionally + # This seems used more conventionally + if sdk_dir is None: sdk_dir = environ.get('ANDROID_HOME', None) - if sdk_dir is None: # Checks in the buildozer SDK dir, useful - # for debug tests of p4a + # Checks in the buildozer SDK dir, useful for debug tests of p4a + if sdk_dir is None: possible_dirs = glob.glob(expanduser(join( '~', '.buildozer', 'android', 'platform', 'android-sdk-*'))) possible_dirs = [d for d in possible_dirs if not @@ -199,57 +212,25 @@ class Context(object): 'maintain your own SDK download.') sdk_dir = possible_dirs[0] if sdk_dir is None: - warning('Android SDK dir was not specified, exiting.') - exit(1) + raise BuildInterruptingException('Android SDK dir was not specified, exiting.') self.sdk_dir = realpath(sdk_dir) # Check what Android API we're using android_api = None if user_android_api: android_api = user_android_api - if android_api is not None: - info('Getting Android API version from user argument') - if android_api is None: - android_api = environ.get('ANDROIDAPI', None) - if android_api is not None: - info('Found Android API target in $ANDROIDAPI') - if android_api is None: + info('Getting Android API version from user argument: {}'.format(android_api)) + elif 'ANDROIDAPI' in environ: + android_api = environ['ANDROIDAPI'] + info('Found Android API target in $ANDROIDAPI: {}'.format(android_api)) + else: info('Android API target was not set manually, using ' - 'the default of {}'.format(DEFAULT_ANDROID_API)) - android_api = DEFAULT_ANDROID_API + 'the default of {}'.format(RECOMMENDED_TARGET_API)) + android_api = RECOMMENDED_TARGET_API android_api = int(android_api) self.android_api = android_api - if self.android_api >= 21 and self.archs[0].arch == 'armeabi': - error('Asked to build for armeabi architecture with API ' - '{}, but API 21 or greater does not support armeabi'.format( - self.android_api)) - error('You probably want to build with --arch=armeabi-v7a instead') - exit(1) - - # try to determinate min_api - android_min_api = None - if user_android_min_api: - android_min_api = user_android_min_api - if android_min_api is not None: - info('Getting Minimum Android API version from user argument') - if android_min_api is None: - android_min_api = environ.get("ANDROIDMINAPI", None) - if android_min_api is not None: - info('Found Android minimum api in $ANDROIDMINAPI') - if android_min_api is None: - info('Minimum Android API was not set, using current Android API ' - '{}'.format(android_api)) - android_min_api = android_api - android_min_api = int(android_min_api) - self.android_min_api = android_min_api - - info("Requested API {} (minimum {})".format( - self.android_api, self.android_min_api)) - - if self.android_min_api > android_api: - error('Android minimum api cannot be higher than Android api') - exit(1) + check_target_api(android_api, self.archs[0].arch) if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')): avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager')) @@ -258,9 +239,9 @@ class Context(object): android = sh.Command(join(sdk_dir, 'tools', 'android')) targets = android('list').stdout.decode('utf-8').split('\n') else: - error('Could not find `android` or `sdkmanager` binaries in ' - 'Android SDK. Exiting.') - exit(1) + raise BuildInterruptingException( + 'Could not find `android` or `sdkmanager` binaries in Android SDK', + instructions='Make sure the path to the Android SDK is correct') apis = [s for s in targets if re.match(r'^ *API level: ', s)] apis = [re.findall(r'[0-9]+', s) for s in apis] apis = [int(s[0]) for s in apis if s] @@ -270,30 +251,28 @@ class Context(object): info(('Requested API target {} is available, ' 'continuing.').format(android_api)) else: - warning(('Requested API target {} is not available, install ' - 'it with the SDK android tool.').format(android_api)) - warning('Exiting.') - exit(1) + raise BuildInterruptingException( + ('Requested API target {} is not available, install ' + 'it with the SDK android tool.').format(android_api)) # Find the Android NDK # Could also use ANDROID_NDK, but doesn't look like many tools use this ndk_dir = None if user_ndk_dir: ndk_dir = user_ndk_dir - if ndk_dir is not None: - info('Getting NDK dir from from user argument') + info('Getting NDK dir from from user argument') if ndk_dir is None: # The old P4A-specific dir ndk_dir = environ.get('ANDROIDNDK', None) if ndk_dir is not None: - info('Found NDK dir in $ANDROIDNDK') + info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir)) if ndk_dir is None: # Apparently the most common convention ndk_dir = environ.get('NDK_HOME', None) if ndk_dir is not None: - info('Found NDK dir in $NDK_HOME') + info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Another convention (with maven?) ndk_dir = environ.get('ANDROID_NDK_HOME', None) if ndk_dir is not None: - info('Found NDK dir in $ANDROID_NDK_HOME') + info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Checks in the buildozer NDK dir, useful # # for debug tests of p4a possible_dirs = glob.glob(expanduser(join( @@ -307,62 +286,31 @@ class Context(object): 'maintain your own NDK download.') ndk_dir = possible_dirs[0] if ndk_dir is None: - warning('Android NDK dir was not specified, exiting.') - exit(1) + raise BuildInterruptingException('Android NDK dir was not specified') self.ndk_dir = realpath(ndk_dir) - # Find the NDK version, and check it against what the NDK dir - # seems to report - ndk_ver = None - if user_ndk_ver: - ndk_ver = user_ndk_ver - if ndk_dir is not None: - info('Got NDK version from from user argument') - if ndk_ver is None: - ndk_ver = environ.get('ANDROIDNDKVER', None) - if ndk_dir is not None: - info('Got NDK version from $ANDROIDNDKVER') + check_ndk_version(ndk_dir) - self.ndk = 'google' + self.ndk = 'crystax' # force crystax detection - try: - with open(join(ndk_dir, 'RELEASE.TXT')) as fileh: - reported_ndk_ver = fileh.read().split(' ')[0].strip() - except IOError: - pass + ndk_api = None + if user_ndk_api: + ndk_api = user_ndk_api + info('Getting NDK API version (i.e. minimum supported API) from user argument') + elif 'NDKAPI' in environ: + ndk_api = environ.get('NDKAPI', None) + info('Found Android API target in $NDKAPI') else: - if reported_ndk_ver.startswith('crystax-ndk-'): - reported_ndk_ver = reported_ndk_ver[12:] - self.ndk = 'crystax' - if ndk_ver is None: - ndk_ver = reported_ndk_ver - info(('Got Android NDK version from the NDK dir: ' - 'it is {}').format(ndk_ver)) - else: - if ndk_ver != reported_ndk_ver: - warning('NDK version was set as {}, but checking ' - 'the NDK dir claims it is {}.'.format( - ndk_ver, reported_ndk_ver)) - warning('The build will try to continue, but it may ' - 'fail and you should check ' - 'that your setting is correct.') - warning('If the NDK dir result is correct, you don\'t ' - 'need to manually set the NDK ver.') - if ndk_ver is None: - warning('Android NDK version could not be found. This probably' - 'won\'t cause any problems, but if necessary you can' - 'set it with `--ndk-version=...`.') - self.ndk_ver = ndk_ver + ndk_api = min(self.android_api, RECOMMENDED_NDK_API) + warning('NDK API target was not set manually, using ' + 'the default of {} = min(android-api={}, default ndk-api={})'.format( + ndk_api, self.android_api, RECOMMENDED_NDK_API)) + ndk_api = int(ndk_api) + self.ndk_api = ndk_api - info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver)) + check_ndk_api(ndk_api, self.android_api) - virtualenv = None - if virtualenv is None: - virtualenv = sh.which('virtualenv2') - if virtualenv is None: - virtualenv = sh.which('virtualenv-2.7') - if virtualenv is None: - virtualenv = sh.which('virtualenv') + virtualenv = get_virtualenv_executable() if virtualenv is None: raise IOError('Couldn\'t find a virtualenv executable, ' 'you must install this to use p4a.') @@ -374,14 +322,13 @@ class Context(object): if not self.ccache: info('ccache is missing, the build will not be optimized in the ' 'future.') - for cython_fn in ("cython2", "cython-2.7", "cython"): + for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"): cython = sh.which(cython_fn) if cython: self.cython = cython break else: - error('No cython binary found. Exiting.') - exit(1) + raise BuildInterruptingException('No cython binary found.') if not self.cython: ok = False warning("Missing requirement: cython is not installed") @@ -394,9 +341,8 @@ class Context(object): self.ndk_platform = join( self.ndk_dir, 'platforms', - 'android-{}'.format(self.android_min_api), + 'android-{}'.format(self.ndk_api), platform_dir) - if not exists(self.ndk_platform): warning('ndk_platform doesn\'t exist: {}'.format( self.ndk_platform)) @@ -408,7 +354,7 @@ class Context(object): toolchain_versions = [] toolchain_path = join(self.ndk_dir, 'toolchains') - if os.path.isdir(toolchain_path): + if isdir(toolchain_path): toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path, toolchain_prefix)) toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:] @@ -456,9 +402,8 @@ class Context(object): executable)) if not ok: - error('{}python-for-android cannot continue; aborting{}'.format( - Err_Fore.RED, Err_Fore.RESET)) - sys.exit(1) + raise BuildInterruptingException( + 'python-for-android cannot continue due to the missing executables above') def __init__(self): super(Context, self).__init__() @@ -469,7 +414,7 @@ class Context(object): self._sdk_dir = None self._ndk_dir = None self._android_api = None - self._ndk_ver = None + self._ndk_api = None self.ndk = None self.toolchain_prefix = None @@ -483,6 +428,7 @@ class Context(object): ArchARM(self), ArchARMv7_a(self), Archx86(self), + Archx86_64(self), ArchAarch_64(self), ) @@ -504,8 +450,7 @@ class Context(object): new_archs.add(match) self.archs = list(new_archs) if not self.archs: - warning('Asked to compile for no Archs, so failing.') - exit(1) + raise BuildInterruptingException('Asked to compile for no Archs, so failing.') info('Will compile for the following archs: {}'.format( ', '.join([arch.arch for arch in self.archs]))) @@ -523,14 +468,10 @@ class Context(object): '''Returns the location of site-packages in the python-install build dir. ''' - - # This needs to be replaced with something more general in - # order to support multiple python versions and/or multiple - # archs. - if self.python_recipe.from_crystax: - return self.get_python_install_dir() - return join(self.get_python_install_dir(), - 'lib', 'python3.7', 'site-packages') + if self.python_recipe.name == 'python2legacy': + return join(self.get_python_install_dir(), + 'lib', 'python2.7', 'site-packages') + return self.get_python_install_dir() def get_libs_dir(self, arch): '''The libs dir for a given arch.''' @@ -541,9 +482,33 @@ class Context(object): return exists(join(self.get_libs_dir(arch), lib)) def has_package(self, name, arch=None): + # If this is a file path, it'll need special handling: + if (name.find("/") >= 0 or name.find("\\") >= 0) and \ + name.find("://") < 0: # (:// would indicate an url) + if not os.path.exists(name): + # Non-existing dir, cannot look this up. + return False + if os.path.exists(os.path.join(name, "setup.py")): + # Get name from setup.py: + name = subprocess.check_output([ + sys.executable, "setup.py", "--name"], + cwd=name) + try: + name = name.decode('utf-8', 'replace') + except AttributeError: + pass + name = name.strip() + if len(name) == 0: + # Failed to look up any meaningful name. + return False + else: + # A folder with whatever, cannot look this up. + return False + + # Try to look up recipe by name: try: recipe = Recipe.get_recipe(name, self) - except IOError: + except ValueError: pass else: name = getattr(recipe, 'site_packages_name', None) or name @@ -562,7 +527,6 @@ class Context(object): def build_recipes(build_order, python_modules, ctx): # Put recipes in correct build order - bs = ctx.bootstrap info_notify("Recipe build order is {}".format(build_order)) if python_modules: python_modules = sorted(set(python_modules)) @@ -635,7 +599,13 @@ def run_pymodules_install(ctx, modules): venv = sh.Command(ctx.virtualenv) with current_directory(join(ctx.build_dir)): - shprint(venv, '--python=python3.7', 'venv') + shprint(venv, + '--python=python{}'.format( + ctx.python_recipe.major_minor_version_string. + partition(".")[0] + ), + 'venv' + ) info('Creating a requirements.txt file for the Python modules') with open('requirements.txt', 'w') as fileh: @@ -647,18 +617,63 @@ def run_pymodules_install(ctx, modules): line = '{}\n'.format(module) fileh.write(line) - info('Installing Python modules with pip') - info('If this fails with a message about /bin/false, this ' - 'probably means the package cannot be installed with ' - 'pip as it needs a compilation recipe.') + # Prepare base environment and upgrade pip: + base_env = copy.copy(os.environ) + base_env["PYTHONPATH"] = ctx.get_site_packages_dir() + info('Upgrade pip to latest version') + shprint(sh.bash, '-c', ( + "source venv/bin/activate && pip install -U pip" + ), _env=copy.copy(base_env)) - # This bash method is what old-p4a used - # It works but should be replaced with something better + # Install Cython in case modules need it to build: + info('Install Cython in case one of the modules needs it to build') + shprint(sh.bash, '-c', ( + "venv/bin/pip install Cython" + ), _env=copy.copy(base_env)) + + # Get environment variables for build (with CC/compiler set): + standard_recipe = CythonRecipe() + standard_recipe.ctx = ctx + # (note: following line enables explicit -lpython... linker options) + standard_recipe.call_hostpython_via_targetpython = False + recipe_env = standard_recipe.get_recipe_env(ctx.archs[0]) + env = copy.copy(base_env) + env.update(recipe_env) + + info('Installing Python modules with pip') + info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. ' + 'A reason for this is often modules compiling ' + 'native code that is unaware of Android cross-compilation ' + 'and does not work without additional ' + 'changes / workarounds.') + + # Make sure our build package dir is available, and the virtualenv + # site packages come FIRST (so the proper pip version is used): + env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir() + env["PYTHONPATH"] = os.path.abspath(join( + ctx.build_dir, "venv", "lib", + "python" + ctx.python_recipe.major_minor_version_string, + "site-packages")) + ":" + env["PYTHONPATH"] + + ''' + # Do actual install: + shprint(sh.bash, '-c', ( + "venv/bin/pip " + + "install -v --target '{0}' --no-deps -r requirements.txt" + ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")), + _env=copy.copy(env)) + ''' + + # use old install script shprint(sh.bash, '-c', ( "source venv/bin/activate && env CC=/bin/false CXX=/bin/false " "PYTHONPATH={0} pip install --target '{0}' --no-deps -r requirements.txt" ).format(ctx.get_site_packages_dir())) + # Strip object files after potential Cython or native code builds: + standard_recipe.strip_object_files(ctx.archs[0], env, + build_dir=ctx.build_dir) + def biglink(ctx, arch): # First, collate object files from each recipe @@ -869,8 +884,8 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None): if needso: lib = needso.group(1) if (lib not in needed_libs - and lib not in found_libs - and lib not in blacklist_libs): + and lib not in found_libs + and lib not in blacklist_libs): needed_libs.append(needso.group(1)) sofiles += found_sofiles diff --git a/p4a/pythonforandroid/distribution.py b/p4a/pythonforandroid/distribution.py index 735b64a7..9fa7b4c6 100644 --- a/p4a/pythonforandroid/distribution.py +++ b/p4a/pythonforandroid/distribution.py @@ -2,9 +2,9 @@ from os.path import exists, join import glob import json -from pythonforandroid.logger import (info, info_notify, warning, - Err_Style, Err_Fore) -from pythonforandroid.util import current_directory +from pythonforandroid.logger import (info, info_notify, warning, Err_Style, Err_Fore) +from pythonforandroid.util import current_directory, BuildInterruptingException +from shutil import rmtree class Distribution(object): @@ -21,6 +21,7 @@ class Distribution(object): needs_build = False # Whether the dist needs compiling url = None dist_dir = None # Where the dist dir ultimately is. Should not be None. + ndk_api = None archs = [] '''The arch targets that the dist is built for.''' @@ -42,9 +43,11 @@ class Distribution(object): @classmethod def get_distribution(cls, ctx, name=None, recipes=[], + ndk_api=None, force_build=False, extra_dist_dirs=[], - require_perfect_match=False): + require_perfect_match=False, + allow_replace_dist=True): '''Takes information about the distribution, and decides what kind of distribution it will be. @@ -68,21 +71,31 @@ class Distribution(object): require_perfect_match : bool If True, will only match distributions with precisely the correct set of recipes. + allow_replace_dist : bool + If True, will allow an existing dist with the specified + name but incompatible requirements to be overwritten by + a new one with the current requirements. ''' existing_dists = Distribution.get_distributions(ctx) - needs_build = True # whether the dist needs building, will be returned - possible_dists = existing_dists + name_match_dist = None + # 0) Check if a dist with that name already exists if name is not None and name: possible_dists = [d for d in possible_dists if d.name == name] + if possible_dists: + name_match_dist = possible_dists[0] # 1) Check if any existing dists meet the requirements _possible_dists = [] for dist in possible_dists: + if ( + ndk_api is not None and dist.ndk_api != ndk_api + ) or dist.ndk_api is None: + continue for recipe in recipes: if recipe not in dist.recipes: break @@ -97,10 +110,12 @@ class Distribution(object): else: info('No existing dists meet the given requirements!') - # If any dist has perfect recipes, return it + # If any dist has perfect recipes and ndk API, return it for dist in possible_dists: if force_build: continue + if ndk_api is not None and dist.ndk_api != ndk_api: + continue if (set(dist.recipes) == set(recipes) or (set(recipes).issubset(set(dist.recipes)) and not require_perfect_match)): @@ -110,33 +125,20 @@ class Distribution(object): assert len(possible_dists) < 2 - if not name and possible_dists: - info('Asked for dist with name {} with recipes ({}), but a dist ' - 'with this name already exists and has incompatible recipes ' - '({})'.format(name, ', '.join(recipes), - ', '.join(possible_dists[0].recipes))) - info('No compatible dist found, so exiting.') - exit(1) - - # # 2) Check if any downloadable dists meet the requirements - - # online_dists = [('testsdl2', ['hostpython2', 'sdl2_image', - # 'sdl2_mixer', 'sdl2_ttf', - # 'python2', 'sdl2', - # 'pyjniussdl2', 'kivysdl2'], - # 'https://github.com/inclement/sdl2-example-dist/archive/master.zip'), - # ] - # _possible_dists = [] - # for dist_name, dist_recipes, dist_url in online_dists: - # for recipe in recipes: - # if recipe not in dist_recipes: - # break - # else: - # dist = Distribution(ctx) - # dist.name = dist_name - # dist.url = dist_url - # _possible_dists.append(dist) - # # if _possible_dists + # If there was a name match but we didn't already choose it, + # then the existing dist is incompatible with the requested + # configuration and the build cannot continue + if name_match_dist is not None and not allow_replace_dist: + raise BuildInterruptingException( + 'Asked for dist with name {name} with recipes ({req_recipes}) and ' + 'NDK API {req_ndk_api}, but a dist ' + 'with this name already exists and has either incompatible recipes ' + '({dist_recipes}) or NDK API {dist_ndk_api}'.format( + name=name, + req_ndk_api=ndk_api, + dist_ndk_api=name_match_dist.ndk_api, + req_recipes=', '.join(recipes), + dist_recipes=', '.join(name_match_dist.recipes))) # If we got this far, we need to build a new dist dist = Distribution(ctx) @@ -152,16 +154,23 @@ class Distribution(object): dist.name = name dist.dist_dir = join(ctx.dist_dir, dist.name) dist.recipes = recipes + dist.ndk_api = ctx.ndk_api return dist + def folder_exists(self): + return exists(self.dist_dir) + + def delete(self): + rmtree(self.dist_dir) + @classmethod def get_distributions(cls, ctx, extra_dist_dirs=[]): '''Returns all the distributions found locally.''' if extra_dist_dirs: - warning('extra_dist_dirs argument to get_distributions ' - 'is not yet implemented') - exit(1) + raise BuildInterruptingException( + 'extra_dist_dirs argument to get_distributions ' + 'is not yet implemented') dist_dir = ctx.dist_dir folders = glob.glob(join(dist_dir, '*')) for dir in extra_dist_dirs: @@ -179,40 +188,47 @@ class Distribution(object): dist.recipes = dist_info['recipes'] if 'archs' in dist_info: dist.archs = dist_info['archs'] + if 'ndk_api' in dist_info: + dist.ndk_api = dist_info['ndk_api'] + else: + dist.ndk_api = None + warning( + "Distribution {distname}: ({distdir}) has been " + "built with an unknown api target, ignoring it, " + "you might want to delete it".format( + distname=dist.name, + distdir=dist.dist_dir + ) + ) dists.append(dist) return dists - def save_info(self): + def save_info(self, dirn): ''' Save information about the distribution in its dist_dir. ''' - with current_directory(self.dist_dir): + with current_directory(dirn): info('Saving distribution info') with open('dist_info.json', 'w') as fileh: - json.dump({'dist_name': self.name, + json.dump({'dist_name': self.ctx.dist_name, + 'bootstrap': self.ctx.bootstrap.name, 'archs': [arch.arch for arch in self.ctx.archs], - 'recipes': self.ctx.recipe_build_order}, + 'ndk_api': self.ctx.ndk_api, + 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules, + 'hostpython': self.ctx.hostpython, + 'python_version': self.ctx.python_recipe.major_minor_version_string}, fileh) - def load_info(self): - '''Load information about the dist from the info file that p4a - automatically creates.''' - with current_directory(self.dist_dir): - filen = 'dist_info.json' - if not exists(filen): - return None - with open('dist_info.json', 'r') as fileh: - dist_info = json.load(fileh) - return dist_info - def pretty_log_dists(dists, log_func=info): infos = [] for dist in dists: - infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: ' + ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api + infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, ' 'includes recipes ({Fore.GREEN}{recipes}' '{Style.RESET_ALL}), built for archs ({Fore.BLUE}' '{archs}{Style.RESET_ALL})'.format( + ndk_api=ndk_api, name=dist.name, recipes=', '.join(dist.recipes), archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN', Fore=Err_Fore, Style=Err_Style)) diff --git a/p4a/pythonforandroid/graph.py b/p4a/pythonforandroid/graph.py index 45f60cbb..2e98e8cc 100644 --- a/p4a/pythonforandroid/graph.py +++ b/p4a/pythonforandroid/graph.py @@ -1,24 +1,37 @@ - from copy import deepcopy from itertools import product -from sys import exit -from pythonforandroid.logger import (info, warning, error) +from pythonforandroid.logger import info from pythonforandroid.recipe import Recipe from pythonforandroid.bootstrap import Bootstrap +from pythonforandroid.util import BuildInterruptingException + + +def fix_deplist(deps): + """ Turn a dependency list into lowercase, and make sure all entries + that are just a string become a tuple of strings + """ + deps = [ + ((dep.lower(),) + if not isinstance(dep, (list, tuple)) + else tuple([dep_entry.lower() + for dep_entry in dep + ])) + for dep in deps + ] + return deps class RecipeOrder(dict): - def __init__(self, ctx): self.ctx = ctx - def conflicts(self, name): + def conflicts(self): for name in self.keys(): try: recipe = Recipe.get_recipe(name, self.ctx) - conflicts = recipe.conflicts - except IOError: + conflicts = [dep.lower() for dep in recipe.conflicts] + except ValueError: conflicts = [] if any([c in self for c in conflicts]): @@ -26,26 +39,59 @@ class RecipeOrder(dict): return False -def recursively_collect_orders(name, ctx, orders=[]): +def get_dependency_tuple_list_for_recipe(recipe, blacklist=None): + """ Get the dependencies of a recipe with filtered out blacklist, and + turned into tuples with fix_deplist() + """ + if blacklist is None: + blacklist = set() + assert(type(blacklist) == set) + if recipe.depends is None: + dependencies = [] + else: + # Turn all dependencies into tuples so that product will work + dependencies = fix_deplist(recipe.depends) + + # Filter out blacklisted items and turn lowercase: + dependencies = [ + tuple(set(deptuple) - blacklist) + for deptuple in dependencies + if tuple(set(deptuple) - blacklist) + ] + return dependencies + + +def recursively_collect_orders( + name, ctx, all_inputs, orders=None, blacklist=None + ): '''For each possible recipe ordering, try to add the new recipe name to that order. Recursively do the same thing with all the dependencies of each recipe. ''' + name = name.lower() + if orders is None: + orders = [] + if blacklist is None: + blacklist = set() try: recipe = Recipe.get_recipe(name, ctx) - if recipe.depends is None: - dependencies = [] - else: - # make all dependencies into lists so that product will work - dependencies = [([dependency] if not isinstance( - dependency, (list, tuple)) - else dependency) for dependency in recipe.depends] + dependencies = get_dependency_tuple_list_for_recipe( + recipe, blacklist=blacklist + ) + + # handle opt_depends: these impose requirements on the build + # order only if already present in the list of recipes to build + dependencies.extend(fix_deplist( + [[d] for d in recipe.get_opt_depends_in_list(all_inputs) + if d.lower() not in blacklist] + )) + if recipe.conflicts is None: conflicts = [] else: - conflicts = recipe.conflicts - except IOError: + conflicts = [dep.lower() for dep in recipe.conflicts] + except ValueError: # The recipe does not exist, so we assume it can be installed # via pip with no extra dependencies dependencies = [] @@ -57,7 +103,7 @@ def recursively_collect_orders(name, ctx, orders=[]): if name in order: new_orders.append(deepcopy(order)) continue - if order.conflicts(name): + if order.conflicts(): continue if any([conflict in order for conflict in conflicts]): continue @@ -69,7 +115,9 @@ def recursively_collect_orders(name, ctx, orders=[]): dependency_new_orders = [new_order] for dependency in dependency_set: dependency_new_orders = recursively_collect_orders( - dependency, ctx, dependency_new_orders) + dependency, ctx, all_inputs, dependency_new_orders, + blacklist=blacklist + ) new_orders.extend(dependency_new_orders) @@ -95,22 +143,142 @@ def find_order(graph): bset.discard(result) -def get_recipe_order_and_bootstrap(ctx, names, bs=None): - recipes_to_load = set(names) - if bs is not None and bs.recipe_depends: - recipes_to_load = recipes_to_load.union(set(bs.recipe_depends)) +def obvious_conflict_checker(ctx, name_tuples, blacklist=None): + """ This is a pre-flight check function that will completely ignore + recipe order or choosing an actual value in any of the multiple + choice tuples/dependencies, and just do a very basic obvious + conflict check. + """ + deps_were_added_by = dict() + deps = set() + if blacklist is None: + blacklist = set() - possible_orders = [] + # Add dependencies for all recipes: + to_be_added = [(name_tuple, None) for name_tuple in name_tuples] + while len(to_be_added) > 0: + current_to_be_added = list(to_be_added) + to_be_added = [] + for (added_tuple, adding_recipe) in current_to_be_added: + assert(type(added_tuple) == tuple) + if len(added_tuple) > 1: + # No obvious commitment in what to add, don't check it itself + # but throw it into deps for later comparing against + # (Remember this function only catches obvious issues) + deps.add(added_tuple) + continue + + name = added_tuple[0] + recipe_conflicts = set() + recipe_dependencies = [] + try: + # Get recipe to add and who's ultimately adding it: + recipe = Recipe.get_recipe(name, ctx) + recipe_conflicts = {c.lower() for c in recipe.conflicts} + recipe_dependencies = get_dependency_tuple_list_for_recipe( + recipe, blacklist=blacklist + ) + except ValueError: + pass + adder_first_recipe_name = adding_recipe or name + + # Collect the conflicts: + triggered_conflicts = [] + for dep_tuple_list in deps: + # See if the new deps conflict with things added before: + if set(dep_tuple_list).intersection( + recipe_conflicts) == set(dep_tuple_list): + triggered_conflicts.append(dep_tuple_list) + continue + + # See if what was added before conflicts with the new deps: + if len(dep_tuple_list) > 1: + # Not an obvious commitment to a specific recipe/dep + # to be added, so we won't check. + # (remember this function only catches obvious issues) + continue + try: + dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx) + except ValueError: + continue + conflicts = [c.lower() for c in dep_recipe.conflicts] + if name in conflicts: + triggered_conflicts.append(dep_tuple_list) + + # Throw error on conflict: + if triggered_conflicts: + # Get first conflict and see who added that one: + adder_second_recipe_name = "'||'".join(triggered_conflicts[0]) + second_recipe_original_adder = deps_were_added_by.get( + (adder_second_recipe_name,), None + ) + if second_recipe_original_adder: + adder_second_recipe_name = second_recipe_original_adder + + # Prompt error: + raise BuildInterruptingException( + "Conflict detected: '{}'" + " inducing dependencies {}, and '{}'" + " inducing conflicting dependencies {}".format( + adder_first_recipe_name, + (recipe.name,), + adder_second_recipe_name, + triggered_conflicts[0] + )) + + # Actually add it to our list: + deps.add(added_tuple) + deps_were_added_by[added_tuple] = adding_recipe + + # Schedule dependencies to be added + to_be_added += [ + (dep, adder_first_recipe_name or name) + for dep in recipe_dependencies + if dep not in deps + ] + # If we came here, then there were no obvious conflicts. + return None + + +def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None): + # Get set of recipe/dependency names, clean up and add bootstrap deps: + names = set(names) + if bs is not None and bs.recipe_depends: + names = names.union(set(bs.recipe_depends)) + names = fix_deplist([ + ([name] if not isinstance(name, (list, tuple)) else name) + for name in names + ]) + if blacklist is None: + blacklist = set() + blacklist = {bitem.lower() for bitem in blacklist} + + # Remove all values that are in the blacklist: + names_before_blacklist = list(names) + names = [] + for name in names_before_blacklist: + cleaned_up_tuple = tuple([ + item for item in name if item not in blacklist + ]) + if cleaned_up_tuple: + names.append(cleaned_up_tuple) + + # Do check for obvious conflicts (that would trigger in any order, and + # without comitting to any specific choice in a multi-choice tuple of + # dependencies): + obvious_conflict_checker(ctx, names, blacklist=blacklist) + # If we get here, no obvious conflicts! # get all possible order graphs, as names may include tuples/lists # of alternative dependencies - names = [([name] if not isinstance(name, (list, tuple)) else name) - for name in names] + possible_orders = [] for name_set in product(*names): new_possible_orders = [RecipeOrder(ctx)] for name in name_set: new_possible_orders = recursively_collect_orders( - name, ctx, orders=new_possible_orders) + name, ctx, name_set, orders=new_possible_orders, + blacklist=blacklist + ) possible_orders.extend(new_possible_orders) # turn each order graph into a linear list if possible @@ -122,23 +290,18 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None): info('Circular dependency found in graph {}, skipping it.'.format( possible_order)) continue - except: - warning('Failed to import recipe named {}; the recipe exists ' - 'but appears broken.'.format(name)) - warning('Exception was:') - raise orders.append(list(order)) - # prefer python2 and SDL2 if available + # prefer python3 and SDL2 if available orders = sorted(orders, - key=lambda order: -('python2' in order) - ('sdl2' in order)) + key=lambda order: -('python3' in order) - ('sdl2' in order)) if not orders: - error('Didn\'t find any valid dependency graphs.') - error('This means that some of your requirements pull in ' - 'conflicting dependencies.') - error('Exiting.') - exit(1) + raise BuildInterruptingException( + 'Didn\'t find any valid dependency graphs. ' + 'This means that some of your ' + 'requirements pull in conflicting dependencies.') + # It would be better to check against possible orders other # than the first one, but in practice clashes will be rare, # and can be resolved by specifying more parameters @@ -153,18 +316,26 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None): if bs is None: bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx) + if bs is None: + # Note: don't remove this without thought, causes infinite loop + raise BuildInterruptingException( + "Could not find any compatible bootstrap!" + ) recipes, python_modules, bs = get_recipe_order_and_bootstrap( - ctx, chosen_order, bs=bs) + ctx, chosen_order, bs=bs, blacklist=blacklist + ) else: # check if each requirement has a recipe recipes = [] python_modules = [] for name in chosen_order: try: - Recipe.get_recipe(name, ctx) - except IOError: + recipe = Recipe.get_recipe(name, ctx) + python_modules += recipe.python_depends + except ValueError: python_modules.append(name) else: recipes.append(name) + python_modules = list(set(python_modules)) return recipes, python_modules, bs diff --git a/p4a/pythonforandroid/logger.py b/p4a/pythonforandroid/logger.py index 38afba0e..4aba39fc 100644 --- a/p4a/pythonforandroid/logger.py +++ b/p4a/pythonforandroid/logger.py @@ -44,9 +44,9 @@ class LevelDifferentiatingFormatter(logging.Formatter): logger = logging.getLogger('p4a') -if not hasattr(logger, 'touched'): # Necessary as importlib reloads - # this, which would add a second - # handler and reset the level +# Necessary as importlib reloads this, +# which would add a second handler and reset the level +if not hasattr(logger, 'touched'): logger.setLevel(logging.INFO) logger.touched = True ch = logging.StreamHandler(stderr) @@ -148,8 +148,10 @@ def shprint(command, *args, **kwargs): kwargs["_bg"] = True is_critical = kwargs.pop('_critical', False) tail_n = kwargs.pop('_tail', None) + full_debug = False if "P4A_FULL_DEBUG" in os.environ: tail_n = 0 + full_debug = True filter_in = kwargs.pop('_filter', None) filter_out = kwargs.pop('_filterout', None) if len(logger.handlers) > 1: @@ -177,16 +179,21 @@ def shprint(command, *args, **kwargs): if isinstance(line, bytes): line = line.decode('utf-8', errors='replace') if logger.level > logging.DEBUG: + if full_debug: + stdout.write(line) + stdout.flush() + continue msg = line.replace( '\n', ' ').replace( '\t', ' ').replace( '\b', ' ').rstrip() if msg: - stdout.write(u'{}\r{}{:<{width}}'.format( - Err_Style.RESET_ALL, msg_hdr, - shorten_string(msg, msg_width), width=msg_width)) - stdout.flush() - need_closing_newline = True + if "CI" not in os.environ: + stdout.write(u'{}\r{}{:<{width}}'.format( + Err_Style.RESET_ALL, msg_hdr, + shorten_string(msg, msg_width), width=msg_width)) + stdout.flush() + need_closing_newline = True else: logger.debug(''.join(['\t', line.rstrip()])) if need_closing_newline: diff --git a/p4a/pythonforandroid/python.py b/p4a/pythonforandroid/python.py new file mode 100755 index 00000000..3a214ee7 --- /dev/null +++ b/p4a/pythonforandroid/python.py @@ -0,0 +1,437 @@ +''' +This module is kind of special because it contains the base classes used to +build our python3 and python2 recipes and his corresponding hostpython recipes. +''' + +from os.path import dirname, exists, join +from multiprocessing import cpu_count +from shutil import copy2 +from os import environ +import subprocess +import glob +import sh + +from pythonforandroid.recipe import Recipe, TargetPythonRecipe +from pythonforandroid.logger import logger, info, shprint +from pythonforandroid.util import ( + current_directory, ensure_dir, walk_valid_filens, + BuildInterruptingException, build_platform) + + +class GuestPythonRecipe(TargetPythonRecipe): + ''' + Class for target python recipes. Sets ctx.python_recipe to point to itself, + so as to know later what kind of Python was built or used. + + This base class is used for our main python recipes (python2 and python3) + which shares most of the build process. + + .. versionadded:: 0.6.0 + Refactored from the inclement's python3 recipe with a few changes: + + - Splits the python's build process several methods: :meth:`build_arch` + and :meth:`get_recipe_env`. + - Adds the attribute :attr:`configure_args`, which has been moved from + the method :meth:`build_arch` into a static class variable. + - Adds some static class variables used to create the python bundle and + modifies the method :meth:`create_python_bundle`, to adapt to the new + situation. The added static class variables are: + :attr:`stdlib_dir_blacklist`, :attr:`stdlib_filen_blacklist`, + :attr:`site_packages_dir_blacklist`and + :attr:`site_packages_filen_blacklist`. + ''' + + MIN_NDK_API = 21 + '''Sets the minimal ndk api number needed to use the recipe. + + .. warning:: This recipe can be built only against API 21+, so it means + that any class which inherits from class:`GuestPythonRecipe` will have + this limitation. + ''' + + from_crystax = False + '''True if the python is used from CrystaX, False otherwise (i.e. if + it is built by p4a).''' + + configure_args = () + '''The configure arguments needed to build the python recipe. Those are + used in method :meth:`build_arch` (if not overwritten like python3crystax's + recipe does). + + .. note:: This variable should be properly set in subclass. + ''' + + stdlib_dir_blacklist = { + '__pycache__', + 'test', + 'tests', + 'lib2to3', + 'ensurepip', + 'idlelib', + 'tkinter', + } + '''The directories that we want to omit for our python bundle''' + + stdlib_filen_blacklist = [ + '*.py', + '*.exe', + '*.whl', + ] + '''The file extensions that we want to blacklist for our python bundle''' + + site_packages_dir_blacklist = { + '__pycache__', + 'tests' + } + '''The directories from site packages dir that we don't want to be included + in our python bundle.''' + + site_packages_filen_blacklist = [ + '*.py' + ] + '''The file extensions from site packages dir that we don't want to be + included in our python bundle.''' + + opt_depends = ['sqlite3', 'libffi', 'openssl'] + '''The optional libraries which we would like to get our python linked''' + + compiled_extension = '.pyc' + '''the default extension for compiled python files. + + .. note:: the default extension for compiled python files has been .pyo for + python 2.x-3.4 but as of Python 3.5, the .pyo filename extension is no + longer used and has been removed in favour of extension .pyc + ''' + + def __init__(self, *args, **kwargs): + self._ctx = None + super(GuestPythonRecipe, self).__init__(*args, **kwargs) + + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + if self.from_crystax: + return super(GuestPythonRecipe, self).get_recipe_env( + arch=arch, with_flags_in_cc=with_flags_in_cc) + + env = environ.copy() + + android_host = env['HOSTARCH'] = arch.command_prefix + toolchain = '{toolchain_prefix}-{toolchain_version}'.format( + toolchain_prefix=self.ctx.toolchain_prefix, + toolchain_version=self.ctx.toolchain_version) + toolchain = join(self.ctx.ndk_dir, 'toolchains', + toolchain, 'prebuilt', build_platform) + + env['CC'] = ( + '{clang} -target {target} -gcc-toolchain {toolchain}').format( + clang=join(self.ctx.ndk_dir, 'toolchains', 'llvm', 'prebuilt', + build_platform, 'bin', 'clang'), + target=arch.target, + toolchain=toolchain) + env['AR'] = join(toolchain, 'bin', android_host) + '-ar' + env['LD'] = join(toolchain, 'bin', android_host) + '-ld' + env['RANLIB'] = join(toolchain, 'bin', android_host) + '-ranlib' + env['READELF'] = join(toolchain, 'bin', android_host) + '-readelf' + env['STRIP'] = join(toolchain, 'bin', android_host) + '-strip' + env['STRIP'] += ' --strip-debug --strip-unneeded' + + env['PATH'] = ( + '{hostpython_dir}:{old_path}').format( + hostpython_dir=self.get_recipe( + 'host' + self.name, self.ctx).get_path_to_python(), + old_path=env['PATH']) + + ndk_flags = ( + '-fPIC --sysroot={ndk_sysroot} -D__ANDROID_API__={android_api} ' + '-isystem {ndk_android_host} -I{ndk_include}').format( + ndk_sysroot=join(self.ctx.ndk_dir, 'sysroot'), + android_api=self.ctx.ndk_api, + ndk_android_host=join( + self.ctx.ndk_dir, 'sysroot', 'usr', 'include', android_host), + ndk_include=join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include')) + sysroot = self.ctx.ndk_platform + env['CFLAGS'] = env.get('CFLAGS', '') + ' ' + ndk_flags + env['CPPFLAGS'] = env.get('CPPFLAGS', '') + ' ' + ndk_flags + env['LDFLAGS'] = env.get('LDFLAGS', '') + ' --sysroot={} -L{}'.format( + sysroot, join(sysroot, 'usr', 'lib')) + + # Manually add the libs directory, and copy some object + # files to the current directory otherwise they aren't + # picked up. This seems necessary because the --sysroot + # setting in LDFLAGS is overridden by the other flags. + # TODO: Work out why this doesn't happen in the original + # bpo-30386 Makefile system. + logger.warning('Doing some hacky stuff to link properly') + lib_dir = join(sysroot, 'usr', 'lib') + if arch.arch == 'x86_64': + lib_dir = join(sysroot, 'usr', 'lib64') + env['LDFLAGS'] += ' -L{}'.format(lib_dir) + shprint(sh.cp, join(lib_dir, 'crtbegin_so.o'), './') + shprint(sh.cp, join(lib_dir, 'crtend_so.o'), './') + + env['SYSROOT'] = sysroot + + if sh.which('lld') is not None: + # Note: The -L. is to fix a bug in python 3.7. + # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234409 + env["LDFLAGS"] += ' -L. -fuse-ld=lld' + else: + logger.warning('lld not found, linking without it. ' + + 'Consider installing lld if linker errors occur.') + + return env + + def set_libs_flags(self, env, arch): + '''Takes care to properly link libraries with python depending on our + requirements and the attribute :attr:`opt_depends`. + ''' + def add_flags(include_flags, link_dirs, link_libs): + env['CPPFLAGS'] = env.get('CPPFLAGS', '') + include_flags + env['LDFLAGS'] = env.get('LDFLAGS', '') + link_dirs + env['LIBS'] = env.get('LIBS', '') + link_libs + + if 'sqlite3' in self.ctx.recipe_build_order: + info('Activating flags for sqlite3') + recipe = Recipe.get_recipe('sqlite3', self.ctx) + add_flags(' -I' + recipe.get_build_dir(arch.arch), + ' -L' + recipe.get_lib_dir(arch), ' -lsqlite3') + + if 'libffi' in self.ctx.recipe_build_order: + info('Activating flags for libffi') + recipe = Recipe.get_recipe('libffi', self.ctx) + # In order to force the correct linkage for our libffi library, we + # set the following variable to point where is our libffi.pc file, + # because the python build system uses pkg-config to configure it. + env['PKG_CONFIG_PATH'] = recipe.get_build_dir(arch.arch) + add_flags(' -I' + ' -I'.join(recipe.get_include_dirs(arch)), + ' -L' + join(recipe.get_build_dir(arch.arch), '.libs'), + ' -lffi') + + if 'openssl' in self.ctx.recipe_build_order: + info('Activating flags for openssl') + recipe = Recipe.get_recipe('openssl', self.ctx) + add_flags(recipe.include_flags(arch), + recipe.link_dirs_flags(arch), recipe.link_libs_flags()) + return env + + def prebuild_arch(self, arch): + super(TargetPythonRecipe, self).prebuild_arch(arch) + if self.from_crystax and self.ctx.ndk != 'crystax': + raise BuildInterruptingException( + 'The {} recipe can only be built when using the CrystaX NDK. ' + 'Exiting.'.format(self.name)) + self.ctx.python_recipe = self + + def build_arch(self, arch): + if self.ctx.ndk_api < self.MIN_NDK_API: + raise BuildInterruptingException( + 'Target ndk-api is {}, but the python3 recipe supports only' + ' {}+'.format(self.ctx.ndk_api, self.MIN_NDK_API)) + + recipe_build_dir = self.get_build_dir(arch.arch) + + # Create a subdirectory to actually perform the build + build_dir = join(recipe_build_dir, 'android-build') + ensure_dir(build_dir) + + # TODO: Get these dynamically, like bpo-30386 does + sys_prefix = '/usr/local' + sys_exec_prefix = '/usr/local' + + with current_directory(build_dir): + env = self.get_recipe_env(arch) + env = self.set_libs_flags(env, arch) + + android_build = sh.Command( + join(recipe_build_dir, + 'config.guess'))().stdout.strip().decode('utf-8') + + if not exists('config.status'): + shprint( + sh.Command(join(recipe_build_dir, 'configure')), + *(' '.join(self.configure_args).format( + android_host=env['HOSTARCH'], + android_build=android_build, + prefix=sys_prefix, + exec_prefix=sys_exec_prefix)).split(' '), + _env=env) + + if not exists('python'): + py_version = self.major_minor_version_string + if self.major_minor_version_string[0] == '3': + py_version += 'm' + shprint(sh.make, 'all', '-j', str(cpu_count()), + 'INSTSONAME=libpython{version}.so'.format( + version=py_version), _env=env) + + # TODO: Look into passing the path to pyconfig.h in a + # better way, although this is probably acceptable + sh.cp('pyconfig.h', join(recipe_build_dir, 'Include')) + + def include_root(self, arch_name): + return join(self.get_build_dir(arch_name), 'Include') + + def link_root(self, arch_name): + return join(self.get_build_dir(arch_name), 'android-build') + + def compile_python_files(self, dir): + ''' + Compile the python files (recursively) for the python files inside + a given folder. + + .. note:: python2 compiles the files into extension .pyo, but in + python3, and as of Python 3.5, the .pyo filename extension is no + longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488) + ''' + args = [self.ctx.hostpython] + if self.ctx.python_recipe.name == 'python3': + args += ['-OO', '-m', 'compileall', '-b', '-f', dir] + else: + args += ['-OO', '-m', 'compileall', '-f', dir] + subprocess.call(args) + + def create_python_bundle(self, dirn, arch): + """ + Create a packaged python bundle in the target directory, by + copying all the modules and standard library to the right + place. + """ + # Todo: find a better way to find the build libs folder + modules_build_dir = join( + self.get_build_dir(arch.arch), + 'android-build', + 'build', + 'lib.linux{}-{}-{}'.format( + '2' if self.version[0] == '2' else '', + arch.command_prefix.split('-')[0], + self.major_minor_version_string + )) + + # Compile to *.pyc/*.pyo the python modules + self.compile_python_files(modules_build_dir) + # Compile to *.pyc/*.pyo the standard python library + self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib')) + # Compile to *.pyc/*.pyo the other python packages (site-packages) + self.compile_python_files(self.ctx.get_python_install_dir()) + + # Bundle compiled python modules to a folder + modules_dir = join(dirn, 'modules') + c_ext = self.compiled_extension + ensure_dir(modules_dir) + module_filens = (glob.glob(join(modules_build_dir, '*.so')) + + glob.glob(join(modules_build_dir, '*' + c_ext))) + info("Copy {} files into the bundle".format(len(module_filens))) + for filen in module_filens: + info(" - copy {}".format(filen)) + copy2(filen, modules_dir) + + # zip up the standard library + stdlib_zip = join(dirn, 'stdlib.zip') + with current_directory(join(self.get_build_dir(arch.arch), 'Lib')): + stdlib_filens = list(walk_valid_filens( + '.', self.stdlib_dir_blacklist, self.stdlib_filen_blacklist)) + info("Zip {} files into the bundle".format(len(stdlib_filens))) + shprint(sh.zip, stdlib_zip, *stdlib_filens) + + # copy the site-packages into place + ensure_dir(join(dirn, 'site-packages')) + ensure_dir(self.ctx.get_python_install_dir()) + # TODO: Improve the API around walking and copying the files + with current_directory(self.ctx.get_python_install_dir()): + filens = list(walk_valid_filens( + '.', self.site_packages_dir_blacklist, + self.site_packages_filen_blacklist)) + info("Copy {} files into the site-packages".format(len(filens))) + for filen in filens: + info(" - copy {}".format(filen)) + ensure_dir(join(dirn, 'site-packages', dirname(filen))) + copy2(filen, join(dirn, 'site-packages', filen)) + + # copy the python .so files into place + python_build_dir = join(self.get_build_dir(arch.arch), + 'android-build') + python_lib_name = 'libpython' + self.major_minor_version_string + if self.major_minor_version_string[0] == '3': + python_lib_name += 'm' + shprint(sh.cp, join(python_build_dir, python_lib_name + '.so'), + join(self.ctx.dist_dir, self.ctx.dist_name, 'libs', arch.arch)) + + info('Renaming .so files to reflect cross-compile') + self.reduce_object_file_names(join(dirn, 'site-packages')) + + return join(dirn, 'site-packages') + + +class HostPythonRecipe(Recipe): + ''' + This is the base class for hostpython3 and hostpython2 recipes. This class + will take care to do all the work to build a hostpython recipe but, be + careful, it is intended to be subclassed because some of the vars needs to + be set: + + - :attr:`name` + - :attr:`version` + + .. versionadded:: 0.6.0 + Refactored from the hostpython3's recipe by inclement + ''' + + name = '' + '''The hostpython's recipe name. This should be ``hostpython2`` or + ``hostpython3`` + + .. warning:: This must be set in inherited class.''' + + version = '' + '''The hostpython's recipe version. + + .. warning:: This must be set in inherited class.''' + + build_subdir = 'native-build' + '''Specify the sub build directory for the hostpython recipe. Defaults + to ``native-build``.''' + + url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz' + '''The default url to download our host python recipe. This url will + change depending on the python version set in attribute :attr:`version`.''' + + def get_build_container_dir(self, arch=None): + choices = self.check_recipe_choices() + dir_name = '-'.join([self.name] + choices) + return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') + + def get_build_dir(self, arch=None): + ''' + .. note:: Unlike other recipes, the hostpython build dir doesn't + depend on the target arch + ''' + return join(self.get_build_container_dir(), self.name) + + def get_path_to_python(self): + return join(self.get_build_dir(), self.build_subdir) + + def build_arch(self, arch): + recipe_build_dir = self.get_build_dir(arch.arch) + + # Create a subdirectory to actually perform the build + build_dir = join(recipe_build_dir, self.build_subdir) + ensure_dir(build_dir) + + if not exists(join(build_dir, 'python')): + with current_directory(recipe_build_dir): + # Configure the build + with current_directory(build_dir): + if not exists('config.status'): + shprint( + sh.Command(join(recipe_build_dir, 'configure'))) + + # Create the Setup file. This copying from Setup.dist + # seems to be the normal and expected procedure. + shprint(sh.cp, join('Modules', 'Setup.dist'), + join(build_dir, 'Modules', 'Setup')) + + shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir) + else: + info('Skipping {name} ({version}) build, as it has already ' + 'been completed'.format(name=self.name, version=self.version)) + + self.ctx.hostpython = join(build_dir, 'python') diff --git a/p4a/pythonforandroid/recipe.py b/p4a/pythonforandroid/recipe.py index 20bab446..b7556dbe 100644 --- a/p4a/pythonforandroid/recipe.py +++ b/p4a/pythonforandroid/recipe.py @@ -1,4 +1,4 @@ -from os.path import basename, dirname, exists, isdir, isfile, join, realpath +from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split import importlib import glob from shutil import rmtree @@ -12,16 +12,16 @@ import shutil import fnmatch from os import listdir, unlink, environ, mkdir, curdir, walk from sys import stdout +import time try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse -from pythonforandroid.logger import (logger, info, warning, error, debug, shprint, info_main) -from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir) +from pythonforandroid.logger import (logger, info, warning, debug, shprint, info_main) +from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir, + BuildInterruptingException) # this import is necessary to keep imp.load_source from complaining :) - - if PY2: import imp import_recipe = imp.load_source @@ -140,13 +140,26 @@ class Recipe(with_metaclass(RecipeMeta)): else: progression = '{0:.2f}%'.format( index * blksize * 100. / float(size)) - stdout.write('- Download {}\r'.format(progression)) - stdout.flush() + if "CI" not in environ: + stdout.write('- Download {}\r'.format(progression)) + stdout.flush() if exists(target): unlink(target) - urlretrieve(url, target, report_hook) + # Download item with multiple attempts (for bad connections): + attempts = 0 + while True: + try: + urlretrieve(url, target, report_hook) + except OSError as e: + attempts += 1 + if attempts >= 5: + raise e + stdout.write('Download failed retrying in a second...') + time.sleep(1) + continue + break return target elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'): if isdir(target): @@ -167,28 +180,18 @@ class Recipe(with_metaclass(RecipeMeta)): shprint(sh.git, 'submodule', 'update', '--recursive') return target - # def get_archive_rootdir(self, filename): - # if filename.endswith(".tgz") or filename.endswith(".tar.gz") or \ - # filename.endswith(".tbz2") or filename.endswith(".tar.bz2"): - # archive = tarfile.open(filename) - # root = archive.next().path.split("/") - # return root[0] - # elif filename.endswith(".zip"): - # with zipfile.ZipFile(filename) as zf: - # return dirname(zf.namelist()[0]) - # else: - # print("Error: cannot detect root directory") - # print("Unrecognized extension for {}".format(filename)) - # raise Exception() - - def apply_patch(self, filename, arch): + def apply_patch(self, filename, arch, build_dir=None): """ Apply a patch from the current recipe directory into the current build directory. + + .. versionchanged:: 0.6.0 + Add ability to apply patch from any dir via kwarg `build_dir`''' """ info("Applying patch {}".format(filename)) + build_dir = build_dir if build_dir else self.get_build_dir(arch) filename = join(self.get_recipe_dir(), filename) - shprint(sh.patch, "-t", "-d", self.get_build_dir(arch), "-p1", + shprint(sh.patch, "-t", "-d", build_dir, "-p1", "-i", filename, _tail=10) def copy_file(self, filename, dest): @@ -206,42 +209,12 @@ class Recipe(with_metaclass(RecipeMeta)): with open(dest, "ab") as fd: fd.write(data) - # def has_marker(self, marker): - # """ - # Return True if the current build directory has the marker set - # """ - # return exists(join(self.build_dir, ".{}".format(marker))) - - # def set_marker(self, marker): - # """ - # Set a marker info the current build directory - # """ - # with open(join(self.build_dir, ".{}".format(marker)), "w") as fd: - # fd.write("ok") - - # def delete_marker(self, marker): - # """ - # Delete a specific marker - # """ - # try: - # unlink(join(self.build_dir, ".{}".format(marker))) - # except: - # pass - @property def name(self): '''The name of the recipe, the same as the folder containing it.''' modname = self.__class__.__module__ return modname.split(".", 2)[-1] - # @property - # def archive_fn(self): - # bfn = basename(self.url.format(version=self.version)) - # fn = "{}/{}-{}".format( - # self.ctx.cache_dir, - # self.name, bfn) - # return fn - @property def filtered_archs(self): '''Return archs of self.ctx that are valid build archs @@ -269,6 +242,12 @@ class Recipe(with_metaclass(RecipeMeta)): recipes.append(recipe) return sorted(recipes) + def get_opt_depends_in_list(self, recipes): + '''Given a list of recipe names, returns those that are also in + self.opt_depends. + ''' + return [recipe for recipe in recipes if recipe in self.opt_depends] + def get_build_container_dir(self, arch): '''Given the arch name, returns the directory where it will be built. @@ -277,7 +256,8 @@ class Recipe(with_metaclass(RecipeMeta)): alternative or optional dependencies are being built. ''' dir_name = self.get_dir_name() - return join(self.ctx.build_dir, 'other_builds', dir_name, arch) + return join(self.ctx.build_dir, 'other_builds', + dir_name, '{}__ndk_target_{}'.format(arch, self.ctx.ndk_api)) def get_dir_name(self): choices = self.check_recipe_choices() @@ -367,7 +347,7 @@ class Recipe(with_metaclass(RecipeMeta)): debug('* Expected md5sum: {}'.format(expected_md5)) raise ValueError( ('Generated md5sum does not match expected md5sum ' - 'for {} recipe').format(self.name)) + 'for {} recipe').format(self.name)) else: info('{} download already cached, skipping'.format(self.name)) @@ -410,24 +390,20 @@ class Recipe(with_metaclass(RecipeMeta)): try: sh.unzip(extraction_filename) except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2): - pass # return code 1 means unzipping had - # warnings but did complete, - # apparently happens sometimes with - # github zips + # return code 1 means unzipping had + # warnings but did complete, + # apparently happens sometimes with + # github zips + pass import zipfile fileh = zipfile.ZipFile(extraction_filename, 'r') root_directory = fileh.filelist[0].filename.split('/')[0] if root_directory != basename(directory_name): shprint(sh.mv, root_directory, directory_name) - elif (extraction_filename.endswith('.tar.gz') or - extraction_filename.endswith('.tgz') or - extraction_filename.endswith('.tar.bz2') or - extraction_filename.endswith('.tbz2') or - extraction_filename.endswith('.tar.xz') or - extraction_filename.endswith('.txz')): + elif extraction_filename.endswith( + ('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')): sh.tar('xf', extraction_filename) - root_directory = shprint( - sh.tar, 'tf', extraction_filename).stdout.decode( + root_directory = sh.tar('tf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) @@ -450,12 +426,12 @@ class Recipe(with_metaclass(RecipeMeta)): else: info('{} is already unpacked, skipping'.format(self.name)) - def get_recipe_env(self, arch=None, with_flags_in_cc=True): + def get_recipe_env(self, arch=None, with_flags_in_cc=True, clang=False): """Return the env specialized for the recipe """ if arch is None: arch = self.filtered_archs[0] - return arch.get_env(with_flags_in_cc=with_flags_in_cc) + return arch.get_env(with_flags_in_cc=with_flags_in_cc, clang=clang) def prebuild_arch(self, arch): '''Run any pre-build tasks for the Recipe. By default, this checks if @@ -471,8 +447,11 @@ class Recipe(with_metaclass(RecipeMeta)): build_dir = self.get_build_dir(arch.arch) return exists(join(build_dir, '.patched')) - def apply_patches(self, arch): - '''Apply any patches for the Recipe.''' + def apply_patches(self, arch, build_dir=None): + '''Apply any patches for the Recipe. + + .. versionchanged:: 0.6.0 + Add ability to apply patches from any dir via kwarg `build_dir`''' if self.patches: info_main('Applying patches for {}[{}]' .format(self.name, arch.arch)) @@ -481,6 +460,7 @@ class Recipe(with_metaclass(RecipeMeta)): info_main('{} already patched, skipping'.format(self.name)) return + build_dir = build_dir if build_dir else self.get_build_dir(arch.arch) for patch in self.patches: if isinstance(patch, (tuple, list)): patch, patch_check = patch @@ -489,9 +469,9 @@ class Recipe(with_metaclass(RecipeMeta)): self.apply_patch( patch.format(version=self.version, arch=arch.arch), - arch.arch) + arch.arch, build_dir=build_dir) - shprint(sh.touch, join(self.get_build_dir(arch.arch), '.patched')) + shprint(sh.touch, join(build_dir, '.patched')) def should_build(self, arch): '''Should perform any necessary test and return True only if it needs @@ -547,8 +527,8 @@ class Recipe(with_metaclass(RecipeMeta)): if exists(base_dir): dirs.append(base_dir) if not dirs: - warning(('Attempted to clean build for {} but found no existing ' - 'build dirs').format(self.name)) + warning('Attempted to clean build for {} but found no existing ' + 'build dirs'.format(self.name)) for directory in dirs: if exists(directory): @@ -595,6 +575,7 @@ class Recipe(with_metaclass(RecipeMeta)): @classmethod def get_recipe(cls, name, ctx): '''Returns the Recipe with the given name, if it exists.''' + name = name.lower() if not hasattr(cls, "recipes"): cls.recipes = {} if name in cls.recipes: @@ -602,20 +583,28 @@ class Recipe(with_metaclass(RecipeMeta)): recipe_file = None for recipes_dir in cls.recipe_dirs(ctx): - recipe_file = join(recipes_dir, name, '__init__.py') - if exists(recipe_file): + if not exists(recipes_dir): + continue + # Find matching folder (may differ in case): + for subfolder in listdir(recipes_dir): + if subfolder.lower() == name: + recipe_file = join(recipes_dir, subfolder, '__init__.py') + if exists(recipe_file): + name = subfolder # adapt to actual spelling + break + recipe_file = None + if recipe_file is not None: break - recipe_file = None if not recipe_file: - raise IOError('Recipe does not exist: {}'.format(name)) + raise ValueError('Recipe does not exist: {}'.format(name)) mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file) if len(logger.handlers) > 1: logger.removeHandler(logger.handlers[1]) recipe = mod.recipe recipe.ctx = ctx - cls.recipes[name] = recipe + cls.recipes[name.lower()] = recipe return recipe @@ -626,8 +615,8 @@ class IncludedFilesBehaviour(object): def prepare_build_dir(self, arch): if self.src_filename is None: - print('IncludedFilesBehaviour failed: no src_filename specified') - exit(1) + raise BuildInterruptingException( + 'IncludedFilesBehaviour failed: no src_filename specified') shprint(sh.rm, '-rf', self.get_build_dir(arch)) shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename), self.get_build_dir(arch)) @@ -640,6 +629,9 @@ class BootstrapNDKRecipe(Recipe): To build an NDK project which is not part of the bootstrap, see :class:`~pythonforandroid.recipe.NDKRecipe`. + + To link with python, call the method :meth:`get_recipe_env` + with the kwarg *with_python=True*. ''' dir_name = None # The name of the recipe build folder in the jni dir @@ -656,6 +648,20 @@ class BootstrapNDKRecipe(Recipe): def get_jni_dir(self): return join(self.ctx.bootstrap.build_dir, 'jni') + def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False): + env = super(BootstrapNDKRecipe, self).get_recipe_env( + arch, with_flags_in_cc) + if not with_python: + return env + + env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch) + env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch) + env['EXTRA_LDLIBS'] = ' -lpython{}'.format( + self.ctx.python_recipe.major_minor_version_string) + if 'python3' in self.ctx.python_recipe.name: + env['EXTRA_LDLIBS'] += 'm' + return env + class NDKRecipe(Recipe): '''A recipe class for any NDK project not included in the bootstrap.''' @@ -682,7 +688,13 @@ class NDKRecipe(Recipe): env = self.get_recipe_env(arch) with current_directory(self.get_build_dir(arch.arch)): - shprint(sh.ndk_build, 'V=1', 'APP_ABI=' + arch.arch, *extra_args, _env=env) + shprint( + sh.ndk_build, + 'V=1', + 'APP_PLATFORM=android-' + str(self.ctx.ndk_api), + 'APP_ABI=' + arch.arch, + *extra_args, _env=env + ) class PythonRecipe(Recipe): @@ -711,6 +723,13 @@ class PythonRecipe(Recipe): setup_extra_args = [] '''List of extra arugments to pass to setup.py''' + def __init__(self, *args, **kwargs): + super(PythonRecipe, self).__init__(*args, **kwargs) + depends = self.depends + depends.append(('python2', 'python2legacy', 'python3', 'python3crystax')) + depends = list(set(depends)) + self.depends = depends + def clean_build(self, arch=None): super(PythonRecipe, self).clean_build(arch=arch) name = self.folder_name @@ -726,14 +745,12 @@ class PythonRecipe(Recipe): @property def real_hostpython_location(self): - if 'hostpython2' in self.ctx.recipe_build_order: - return join( - Recipe.get_recipe('hostpython2', self.ctx).get_build_dir(), - 'hostpython') - elif 'hostpython3crystax' in self.ctx.recipe_build_order: - return join( - Recipe.get_recipe('hostpython3crystax', self.ctx).get_build_dir(), - 'hostpython') + host_name = 'host{}'.format(self.ctx.python_recipe.name) + host_build = Recipe.get_recipe(host_name, self.ctx).get_build_dir() + if host_name in ['hostpython2', 'hostpython3']: + return join(host_build, 'native-build', 'python') + elif host_name in ['hostpython3crystax', 'hostpython2legacy']: + return join(host_build, 'hostpython') else: python_recipe = self.ctx.python_recipe return 'python{}'.format(python_recipe.version) @@ -757,17 +774,28 @@ class PythonRecipe(Recipe): env['PYTHONNOUSERSITE'] = '1' + # Set the LANG, this isn't usually important but is a better default + # as it occasionally matters how Python e.g. reads files + env['LANG'] = "en_GB.UTF-8" + if not self.call_hostpython_via_targetpython: # sets python headers/linkages...depending on python's recipe + python_name = self.ctx.python_recipe.name python_version = self.ctx.python_recipe.version python_short_version = '.'.join(python_version.split('.')[:2]) - if 'python2' in self.ctx.recipe_build_order: - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env[ - 'PYTHON_ROOT'] + '/include/python2.7' - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -lpython2.7' - elif self.ctx.python_recipe.from_crystax: + if not self.ctx.python_recipe.from_crystax: + env['CFLAGS'] += ' -I{}'.format( + self.ctx.python_recipe.include_root(arch.arch)) + env['LDFLAGS'] += ' -L{} -lpython{}'.format( + self.ctx.python_recipe.link_root(arch.arch), + self.ctx.python_recipe.major_minor_version_string) + if python_name == 'python3': + env['LDFLAGS'] += 'm' + elif python_name == 'python2legacy': + env['PYTHON_ROOT'] = join( + self.ctx.python_recipe.get_build_dir( + arch.arch), 'python-install') + else: ndk_dir_python = join(self.ctx.ndk_dir, 'sources', 'python', python_version) env['CFLAGS'] += ' -I{} '.format( @@ -776,26 +804,19 @@ class PythonRecipe(Recipe): env['LDFLAGS'] += ' -L{}'.format( join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_short_version) - elif 'python3' in self.ctx.recipe_build_order: - # This headers are unused cause python3 recipe was removed - # TODO: should be reviewed when python3 recipe added - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env[ - 'PYTHON_ROOT'] + '/include/python{}m'.format( - python_short_version) - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -lpython{}m'.format( - python_short_version) + hppath = [] hppath.append(join(dirname(self.hostpython_location), 'Lib')) hppath.append(join(hppath[0], 'site-packages')) builddir = join(dirname(self.hostpython_location), 'build') - hppath += [join(builddir, d) for d in listdir(builddir) - if isdir(join(builddir, d))] - if 'PYTHONPATH' in env: - env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']]) - else: - env['PYTHONPATH'] = ':'.join(hppath) + if exists(builddir): + hppath += [join(builddir, d) for d in listdir(builddir) + if isdir(join(builddir, d))] + if len(hppath) > 0: + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']]) + else: + env['PYTHONPATH'] = ':'.join(hppath) return env def should_build(self, arch): @@ -826,7 +847,7 @@ class PythonRecipe(Recipe): with current_directory(self.get_build_dir(arch.arch)): hostpython = sh.Command(self.hostpython_location) - if self.ctx.python_recipe.from_crystax: + if self.ctx.python_recipe.name != 'python2legacy': hpenv = env.copy() shprint(hostpython, 'setup.py', 'install', '-O2', '--root={}'.format(self.ctx.get_python_install_dir()), @@ -835,13 +856,11 @@ class PythonRecipe(Recipe): elif self.call_hostpython_via_targetpython: shprint(hostpython, 'setup.py', 'install', '-O2', _env=env, *self.setup_extra_args) - else: - hppath = join(dirname(self.hostpython_location), 'Lib', - 'site-packages') + else: # python2legacy + hppath = join(dirname(self.hostpython_location), 'Lib', 'site-packages') hpenv = env.copy() if 'PYTHONPATH' in hpenv: - hpenv['PYTHONPATH'] = ':'.join([hppath] + - hpenv['PYTHONPATH'].split(':')) + hpenv['PYTHONPATH'] = ':'.join([hppath] + hpenv['PYTHONPATH'].split(':')) else: hpenv['PYTHONPATH'] = hppath shprint(hostpython, 'setup.py', 'install', '-O2', @@ -920,12 +939,14 @@ class CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe): arch_noeabi=arch.arch.replace('eabi', '') ) env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['CFLAGS'] += " -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" \ - " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" \ - " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include".format(**keys) + env['CFLAGS'] += ( + " -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" + + " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" + + " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include").format(**keys) env['CXXFLAGS'] = env['CFLAGS'] + ' -frtti -fexceptions' - env['LDFLAGS'] += " -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" \ - " -lgnustl_shared".format(**keys) + env['LDFLAGS'] += ( + " -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" + + " -lgnustl_shared").format(**keys) return env @@ -949,7 +970,7 @@ class CythonRecipe(PythonRecipe): def __init__(self, *args, **kwargs): super(CythonRecipe, self).__init__(*args, **kwargs) depends = self.depends - depends.append(('python2', 'python3crystax')) + depends.append(('python2', 'python2legacy', 'python3', 'python3crystax')) depends = list(set(depends)) self.depends = depends @@ -966,20 +987,10 @@ class CythonRecipe(PythonRecipe): env = self.get_recipe_env(arch) - if self.ctx.python_recipe.from_crystax: - command = sh.Command('python{}'.format(self.ctx.python_recipe.version)) - site_packages_dirs = command( - '-c', 'import site; print("\\n".join(site.getsitepackages()))') - site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n') - if 'PYTHONPATH' in env: - env['PYTHONPATH'] = env['PYTHONPATH'] + ':{}'.format(':'.join(site_packages_dirs)) - else: - env['PYTHONPATH'] = ':'.join(site_packages_dirs) - with current_directory(self.get_build_dir(arch.arch)): hostpython = sh.Command(self.ctx.hostpython) shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env) - print('cwd is', realpath(curdir)) + debug('cwd is {}'.format(realpath(curdir))) info('Trying first build of {} to get cython files: this is ' 'expected to fail'.format(self.name)) @@ -1000,14 +1011,19 @@ class CythonRecipe(PythonRecipe): info('First build appeared to complete correctly, skipping manual' 'cythonising.') - if 'python2' in self.ctx.recipe_build_order: + self.strip_object_files(arch, env) + + def strip_object_files(self, arch, env, build_dir=None): + if build_dir is None: + build_dir = self.get_build_dir(arch.arch) + with current_directory(build_dir): + info('Stripping object files') + if self.ctx.python_recipe.name == 'python2legacy': info('Stripping object files') build_lib = glob.glob('./build/lib*') shprint(sh.find, build_lib[0], '-name', '*.o', '-exec', env['STRIP'], '{}', ';', _env=env) - - if 'python3crystax' in self.ctx.recipe_build_order: - info('Stripping object files') + else: shprint(sh.find, '.', '-iname', '*.so', '-exec', '/usr/bin/echo', '{}', ';', _env=env) shprint(sh.find, '.', '-iname', '*.so', '-exec', @@ -1050,11 +1066,11 @@ class CythonRecipe(PythonRecipe): if self.ctx.python_recipe.from_crystax: env['LDFLAGS'] = (env['LDFLAGS'] + ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))) - # ' -L/home/asandy/.local/share/python-for-android/build/bootstrap_builds/sdl2/libs/armeabi ' - if self.ctx.python_recipe.from_crystax: - env['LDSHARED'] = env['CC'] + ' -shared' - else: + + if self.ctx.python_recipe.name == 'python2legacy': env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh') + else: + env['LDSHARED'] = env['CC'] + ' -shared' # shprint(sh.whereis, env['LDSHARED'], _env=env) env['LIBLINK'] = 'NOTNONE' env['NDKPLATFORM'] = self.ctx.ndk_platform @@ -1068,6 +1084,24 @@ class CythonRecipe(PythonRecipe): env['LIBLINK_PATH'] = liblink_path ensure_dir(liblink_path) + # Add crystax-specific site packages: + if self.ctx.python_recipe.from_crystax: + command = sh.Command('python{}'.format(self.ctx.python_recipe.version)) + site_packages_dirs = command( + '-c', 'import site; print("\\n".join(site.getsitepackages()))') + site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n') + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = env['PYTHONPATH'] +\ + ':{}'.format(':'.join(site_packages_dirs)) + else: + env['PYTHONPATH'] = ':'.join(site_packages_dirs) + while env['PYTHONPATH'].find("::") > 0: + env['PYTHONPATH'] = env['PYTHONPATH'].replace("::", ":") + if env['PYTHONPATH'].endswith(":"): + env['PYTHONPATH'] = env['PYTHONPATH'][:-1] + if env['PYTHONPATH'].startswith(":"): + env['PYTHONPATH'] = env['PYTHONPATH'][1:] + return env @@ -1086,19 +1120,44 @@ class TargetPythonRecipe(Recipe): def prebuild_arch(self, arch): super(TargetPythonRecipe, self).prebuild_arch(arch) if self.from_crystax and self.ctx.ndk != 'crystax': - error('The {} recipe can only be built when ' - 'using the CrystaX NDK. Exiting.'.format(self.name)) - exit(1) + raise BuildInterruptingException( + 'The {} recipe can only be built when ' + 'using the CrystaX NDK. Exiting.'.format(self.name)) self.ctx.python_recipe = self - # @property - # def ctx(self): - # return self._ctx + def include_root(self, arch): + '''The root directory from which to include headers.''' + raise NotImplementedError('Not implemented in TargetPythonRecipe') - # @ctx.setter - # def ctx(self, ctx): - # self._ctx = ctx - # ctx.python_recipe = self + def link_root(self): + raise NotImplementedError('Not implemented in TargetPythonRecipe') + + @property + def major_minor_version_string(self): + from distutils.version import LooseVersion + return '.'.join([str(v) for v in LooseVersion(self.version).version[:2]]) + + def create_python_bundle(self, dirn, arch): + """ + Create a packaged python bundle in the target directory, by + copying all the modules and standard library to the right + place. + """ + raise NotImplementedError('{} does not implement create_python_bundle'.format(self)) + + def reduce_object_file_names(self, dirn): + """Recursively renames all files named XXX.cpython-...-linux-gnu.so" + to "XXX.so", i.e. removing the erroneous architecture name + coming from the local system. + """ + py_so_files = shprint(sh.find, dirn, '-iname', '*.so') + filens = py_so_files.stdout.decode('utf-8').split('\n')[:-1] + for filen in filens: + file_dirname, file_basename = split(filen) + parts = file_basename.split('.') + if len(parts) <= 2: + continue + shprint(sh.mv, filen, join(file_dirname, parts[0] + '.so')) def md5sum(filen): diff --git a/p4a/pythonforandroid/recipes/Pillow/__init__.py b/p4a/pythonforandroid/recipes/Pillow/__init__.py new file mode 100644 index 00000000..14c9d2b2 --- /dev/null +++ b/p4a/pythonforandroid/recipes/Pillow/__init__.py @@ -0,0 +1,59 @@ +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from os.path import join + + +class PillowRecipe(CompiledComponentsPythonRecipe): + + version = '5.2.0' + url = 'https://github.com/python-pillow/Pillow/archive/{version}.tar.gz' + site_packages_name = 'Pillow' + depends = ['png', 'jpeg', 'freetype', 'setuptools'] + patches = [join('patches', 'fix-docstring.patch'), + join('patches', 'fix-setup.patch')] + + call_hostpython_via_targetpython = False + + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + env = super(PillowRecipe, self).get_recipe_env(arch, with_flags_in_cc) + + env['ANDROID_ROOT'] = join(self.ctx.ndk_platform, 'usr') + ndk_lib_dir = join(self.ctx.ndk_platform, 'usr', 'lib') + ndk_include_dir = join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include') + + png = self.get_recipe('png', self.ctx) + png_lib_dir = png.get_lib_dir(arch) + png_jni_dir = png.get_jni_dir(arch) + + jpeg = self.get_recipe('jpeg', self.ctx) + jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch) + + freetype = self.get_recipe('freetype', self.ctx) + free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs') + free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include') + + # harfbuzz is a direct dependency of freetype and we need the proper + # flags to successfully build the Pillow recipe, so we add them here. + harfbuzz = self.get_recipe('harfbuzz', self.ctx) + harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs') + harf_inc_dir = harfbuzz.get_build_dir(arch.arch) + + env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_inc_dir) + env['FREETYPE_ROOT'] = '{}|{}'.format(free_lib_dir, free_inc_dir) + env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir) + + cflags = ' -I{}'.format(png_jni_dir) + cflags += ' -I{} -I{}'.format(harf_inc_dir, join(harf_inc_dir, 'src')) + cflags += ' -I{}'.format(free_inc_dir) + cflags += ' -I{}'.format(jpeg_inc_dir) + cflags += ' -I{}'.format(ndk_include_dir) + + env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg' + + env['LDFLAGS'] += ' -L{} -L{} -L{} -L{}'.format( + png_lib_dir, harf_lib_dir, jpeg_lib_dir, ndk_lib_dir) + if cflags not in env['CFLAGS']: + env['CFLAGS'] += cflags + return env + + +recipe = PillowRecipe() diff --git a/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch b/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch new file mode 100644 index 00000000..ee22e985 --- /dev/null +++ b/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch @@ -0,0 +1,13 @@ +diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py +index a07280e..6b9fe99 100644 +--- a/src/PIL/__init__.py ++++ b/src/PIL/__init__.py +@@ -24,7 +24,7 @@ PILLOW_VERSION = __version__ = _version.__version__ + + del _version + +-__doc__ = __doc__.format(__version__) # include version in docstring ++__doc__ = '' + + + _plugins = ['BlpImagePlugin', diff --git a/p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch b/p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch new file mode 100644 index 00000000..3b0ccef3 --- /dev/null +++ b/p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch @@ -0,0 +1,148 @@ +diff --git a/setup.py b/setup.py +index 761d552..4ddc598 100755 +--- a/setup.py ++++ b/setup.py +@@ -136,12 +136,12 @@ except (ImportError, OSError): + + NAME = 'Pillow' + PILLOW_VERSION = get_version() +-JPEG_ROOT = None ++JPEG_ROOT = tuple(os.environ['JPEG_ROOT'].split('|')) if 'JPEG_ROOT' in os.environ else None + JPEG2K_ROOT = None +-ZLIB_ROOT = None ++ZLIB_ROOT = tuple(os.environ['ZLIB_ROOT'].split('|')) if 'ZLIB_ROOT' in os.environ else None + IMAGEQUANT_ROOT = None + TIFF_ROOT = None +-FREETYPE_ROOT = None ++FREETYPE_ROOT = tuple(os.environ['FREETYPE_ROOT'].split('|')) if 'FREETYPE_ROOT' in os.environ else None + LCMS_ROOT = None + + +@@ -194,7 +194,7 @@ class pil_build_ext(build_ext): + ] + + def initialize_options(self): +- self.disable_platform_guessing = None ++ self.disable_platform_guessing = True + build_ext.initialize_options(self) + for x in self.feature: + setattr(self, 'disable_%s' % x, None) +@@ -466,61 +466,6 @@ class pil_build_ext(build_ext): + feature.jpeg = "libjpeg" # alternative name + + feature.openjpeg_version = None +- if feature.want('jpeg2000'): +- _dbg('Looking for jpeg2000') +- best_version = None +- best_path = None +- +- # Find the best version +- for directory in self.compiler.include_dirs: +- _dbg('Checking for openjpeg-#.# in %s', directory) +- try: +- listdir = os.listdir(directory) +- except Exception: +- # WindowsError, FileNotFoundError +- continue +- for name in listdir: +- if name.startswith('openjpeg-') and \ +- os.path.isfile(os.path.join(directory, name, +- 'openjpeg.h')): +- _dbg('Found openjpeg.h in %s/%s', (directory, name)) +- version = tuple(int(x) for x in name[9:].split('.')) +- if best_version is None or version > best_version: +- best_version = version +- best_path = os.path.join(directory, name) +- _dbg('Best openjpeg version %s so far in %s', +- (best_version, best_path)) +- +- if best_version and _find_library_file(self, 'openjp2'): +- # Add the directory to the include path so we can include +- # rather than having to cope with the versioned +- # include path +- # FIXME (melvyn-sopacua): +- # At this point it's possible that best_path is already in +- # self.compiler.include_dirs. Should investigate how that is +- # possible. +- _add_directory(self.compiler.include_dirs, best_path, 0) +- feature.jpeg2000 = 'openjp2' +- feature.openjpeg_version = '.'.join(str(x) for x in best_version) +- +- if feature.want('imagequant'): +- _dbg('Looking for imagequant') +- if _find_include_file(self, 'libimagequant.h'): +- if _find_library_file(self, "imagequant"): +- feature.imagequant = "imagequant" +- elif _find_library_file(self, "libimagequant"): +- feature.imagequant = "libimagequant" +- +- if feature.want('tiff'): +- _dbg('Looking for tiff') +- if _find_include_file(self, 'tiff.h'): +- if _find_library_file(self, "tiff"): +- feature.tiff = "tiff" +- if sys.platform == "win32" and _find_library_file(self, "libtiff"): +- feature.tiff = "libtiff" +- if (sys.platform == "darwin" and +- _find_library_file(self, "libtiff")): +- feature.tiff = "libtiff" + + if feature.want('freetype'): + _dbg('Looking for freetype') +@@ -546,36 +491,6 @@ class pil_build_ext(build_ext): + if subdir: + _add_directory(self.compiler.include_dirs, subdir, 0) + +- if feature.want('lcms'): +- _dbg('Looking for lcms') +- if _find_include_file(self, "lcms2.h"): +- if _find_library_file(self, "lcms2"): +- feature.lcms = "lcms2" +- elif _find_library_file(self, "lcms2_static"): +- # alternate Windows name. +- feature.lcms = "lcms2_static" +- +- if feature.want('webp'): +- _dbg('Looking for webp') +- if (_find_include_file(self, "webp/encode.h") and +- _find_include_file(self, "webp/decode.h")): +- # In Google's precompiled zip it is call "libwebp": +- if _find_library_file(self, "webp"): +- feature.webp = "webp" +- elif _find_library_file(self, "libwebp"): +- feature.webp = "libwebp" +- +- if feature.want('webpmux'): +- _dbg('Looking for webpmux') +- if (_find_include_file(self, "webp/mux.h") and +- _find_include_file(self, "webp/demux.h")): +- if (_find_library_file(self, "webpmux") and +- _find_library_file(self, "webpdemux")): +- feature.webpmux = "webpmux" +- if (_find_library_file(self, "libwebpmux") and +- _find_library_file(self, "libwebpdemux")): +- feature.webpmux = "libwebpmux" +- + for f in feature: + if not getattr(feature, f) and feature.require(f): + if f in ('jpeg', 'zlib'): +@@ -612,8 +527,6 @@ class pil_build_ext(build_ext): + defs.append(("HAVE_LIBTIFF", None)) + if sys.platform == "win32": + libs.extend(["kernel32", "user32", "gdi32"]) +- if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1: +- defs.append(("WORDS_BIGENDIAN", None)) + + if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW): + defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION)) +@@ -658,10 +571,6 @@ class pil_build_ext(build_ext): + define_macros=defs)) + + tk_libs = ['psapi'] if sys.platform == 'win32' else [] +- exts.append(Extension("PIL._imagingtk", +- ["src/_imagingtk.c", "src/Tk/tkImaging.c"], +- include_dirs=['src/Tk'], +- libraries=tk_libs)) + + exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"])) + exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"])) diff --git a/p4a/pythonforandroid/recipes/android/__init__.py b/p4a/pythonforandroid/recipes/android/__init__.py index a99b57ad..4a06ca80 100644 --- a/p4a/pythonforandroid/recipes/android/__init__.py +++ b/p4a/pythonforandroid/recipes/android/__init__.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour from pythonforandroid.util import current_directory from pythonforandroid.patching import will_build @@ -13,7 +14,8 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe): src_filename = 'src' - depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')] + depends = [('pygame', 'sdl2', 'genericndkbuild'), + 'pyjnius'] config_env = {} @@ -24,26 +26,35 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe): def prebuild_arch(self, arch): super(AndroidRecipe, self).prebuild_arch(arch) + ctx_bootstrap = self.ctx.bootstrap.name + # define macros for Cython, C, Python tpxi = 'DEF {} = {}\n' th = '#define {} {}\n' tpy = '{} = {}\n' - bootstrap = bootstrap_name = self.ctx.bootstrap.name - is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3') + # make sure bootstrap name is in unicode + if isinstance(ctx_bootstrap, bytes): + ctx_bootstrap = ctx_bootstrap.decode('utf-8') + bootstrap = bootstrap_name = ctx_bootstrap + + is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle') is_pygame = bootstrap_name in ('pygame',) is_webview = bootstrap_name in ('webview',) if is_sdl2 or is_webview: if is_sdl2: bootstrap = 'sdl2' - java_ns = 'org.kivy.android' - jni_ns = 'org/kivy/android' + java_ns = u'org.kivy.android' + jni_ns = u'org/kivy/android' elif is_pygame: - java_ns = 'org.renpy.android' - jni_ns = 'org/renpy/android' + java_ns = u'org.renpy.android' + jni_ns = u'org/renpy/android' else: - logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name)) + logger.error(( + 'unsupported bootstrap for android recipe: {}' + ''.format(bootstrap_name) + )) exit(1) config = { @@ -55,22 +66,30 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe): 'JNI_NAMESPACE': jni_ns, } - with current_directory(self.get_build_dir(arch.arch)): - with open(join('android', 'config.pxi'), 'w') as fpxi: - with open(join('android', 'config.h'), 'w') as fh: - with open(join('android', 'config.py'), 'w') as fpy: - for key, value in config.items(): - fpxi.write(tpxi.format(key, repr(value))) - fpy.write(tpy.format(key, repr(value))) - fh.write(th.format(key, value if isinstance(value, int) - else '"{}"'.format(value))) - self.config_env[key] = str(value) + # create config files for Cython, C and Python + with ( + current_directory(self.get_build_dir(arch.arch))), ( + open(join('android', 'config.pxi'), 'w')) as fpxi, ( + open(join('android', 'config.h'), 'w')) as fh, ( + open(join('android', 'config.py'), 'w')) as fpy: - if is_sdl2: - fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n') - fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n') - elif is_pygame: - fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n') + for key, value in config.items(): + fpxi.write(tpxi.format(key, repr(value))) + fpy.write(tpy.format(key, repr(value))) + + fh.write(th.format( + key, + value if isinstance(value, int) else '"{}"'.format(value) + )) + self.config_env[key] = str(value) + + if is_sdl2: + fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n') + fh.write( + '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n' + ) + elif is_pygame: + fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n') recipe = AndroidRecipe() diff --git a/p4a/pythonforandroid/recipes/android/src/android/__init__.py b/p4a/pythonforandroid/recipes/android/src/android/__init__.py index c50c7613..cb95734c 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/__init__.py +++ b/p4a/pythonforandroid/recipes/android/src/android/__init__.py @@ -5,4 +5,4 @@ Android module ''' # legacy import -from android._android import * +from android._android import * # noqa: F401, F403 diff --git a/p4a/pythonforandroid/recipes/android/src/android/_android.pyx b/p4a/pythonforandroid/recipes/android/src/android/_android.pyx index f4f37d8a..d332eedf 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/_android.pyx +++ b/p4a/pythonforandroid/recipes/android/src/android/_android.pyx @@ -175,13 +175,13 @@ api_version = autoclass('android.os.Build$VERSION').SDK_INT version_codes = autoclass('android.os.Build$VERSION_CODES') -python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity') -Rect = autoclass('android.graphics.Rect') +python_act = autoclass(JAVA_NAMESPACE + u'.PythonActivity') +Rect = autoclass(u'android.graphics.Rect') mActivity = python_act.mActivity if mActivity: # PyGame backend already has the listener so adding # one here leads to a crash/too much cpu usage. - # SDL2 now does noe need the listener so there is + # SDL2 now does not need the listener so there is # no point adding a processor intensive layout listenere here. height = 0 def get_keyboard_height(): @@ -332,7 +332,7 @@ class AndroidBrowser(object): return open_url(url) import webbrowser -webbrowser.register('android', AndroidBrowser, None, -1) +webbrowser.register('android', AndroidBrowser) cdef extern void android_start_service(char *, char *, char *) def start_service(title=None, description=None, arg=None): diff --git a/p4a/pythonforandroid/recipes/android/src/android/activity.py b/p4a/pythonforandroid/recipes/android/src/android/activity.py index 94e08e71..cafbbdab 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/activity.py +++ b/p4a/pythonforandroid/recipes/android/src/android/activity.py @@ -1,11 +1,13 @@ -from jnius import PythonJavaClass, java_method, autoclass, cast +from jnius import PythonJavaClass, autoclass, java_method from android.config import JAVA_NAMESPACE, JNI_NAMESPACE _activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity _callbacks = { 'on_new_intent': [], - 'on_activity_result': [] } + 'on_activity_result': [], +} + class NewIntentListener(PythonJavaClass): __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener'] @@ -46,6 +48,7 @@ def bind(**kwargs): _activity.registerActivityResultListener(listener) _callbacks[event].append(listener) + def unbind(**kwargs): for event, callback in kwargs.items(): if event not in _callbacks: @@ -58,4 +61,3 @@ def unbind(**kwargs): _activity.unregisterNewIntentListener(listener) elif event == 'on_activity_result': _activity.unregisterActivityResultListener(listener) - diff --git a/p4a/pythonforandroid/recipes/android/src/android/billing.py b/p4a/pythonforandroid/recipes/android/src/android/billing.py index 46715dc9..0ea10083 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/billing.py +++ b/p4a/pythonforandroid/recipes/android/src/android/billing.py @@ -3,5 +3,3 @@ Android Billing API =================== ''' - -from android._android_billing import * diff --git a/p4a/pythonforandroid/recipes/android/src/android/broadcast.py b/p4a/pythonforandroid/recipes/android/src/android/broadcast.py index ba3dfc97..cb34cd9d 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/broadcast.py +++ b/p4a/pythonforandroid/recipes/android/src/android/broadcast.py @@ -28,7 +28,7 @@ class BroadcastReceiver(object): def _expand_partial_name(partial_name): if '.' in partial_name: - return partial_name # Its actually a full dotted name + return partial_name # Its actually a full dotted name else: name = 'ACTION_{}'.format(partial_name.upper()) if not hasattr(Intent, name): @@ -61,8 +61,8 @@ class BroadcastReceiver(object): Handler = autoclass('android.os.Handler') self.handlerthread.start() self.handler = Handler(self.handlerthread.getLooper()) - self.context.registerReceiver(self.receiver, self.receiver_filter, None, - self.handler) + self.context.registerReceiver( + self.receiver, self.receiver_filter, None, self.handler) def stop(self): self.context.unregisterReceiver(self.receiver) @@ -76,4 +76,3 @@ class BroadcastReceiver(object): return PythonService.mService PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity') return PythonActivity.mActivity - diff --git a/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py b/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py new file mode 100644 index 00000000..1dc1b670 --- /dev/null +++ b/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py @@ -0,0 +1,7 @@ + +from jnius import autoclass + + +def hide_loading_screen(): + python_activity = autoclass('org.kivy.android.PythonActivity') + python_activity.removeLoadingScreen() diff --git a/p4a/pythonforandroid/recipes/android/src/android/mixer.py b/p4a/pythonforandroid/recipes/android/src/android/mixer.py index 4ac224a7..303a9530 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/mixer.py +++ b/p4a/pythonforandroid/recipes/android/src/android/mixer.py @@ -8,36 +8,45 @@ import os condition = threading.Condition() + def periodic(): for i in range(0, num_channels): if i in channels: channels[i].periodic() + num_channels = 8 reserved_channels = 0 + def init(frequency=22050, size=-16, channels=2, buffer=4096): return None + def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096): return None + def quit(): stop() return None + def stop(): for i in range(0, num_channels): sound.stop(i) + def pause(): for i in range(0, num_channels): sound.pause(i) + def unpause(): for i in range(0, num_channels): sound.unpause(i) + def get_busy(): for i in range(0, num_channels): if sound.busy(i): @@ -45,28 +54,33 @@ def get_busy(): return False + def fadeout(time): # Fadeout doesn't work - it just immediately stops playback. stop() # A map from channel number to Channel object. -channels = { } +channels = {} + def set_num_channels(count): global num_channels num_channels = count + def get_num_channels(count): return num_channels + def set_reserved(count): global reserved_channels reserved_channels = count + def find_channel(force=False): - busy = [ ] + busy = [] for i in range(reserved_channels, num_channels): c = Channel(i) @@ -79,10 +93,11 @@ def find_channel(force=False): if not force: return None - busy.sort(key=lambda x : x.play_time) + busy.sort(key=lambda x: x.play_time) return busy[0] + class ChannelImpl(object): def __init__(self, id): @@ -101,7 +116,6 @@ class ChannelImpl(object): if self.loop is not None and sound.queue_depth(self.id) < 2: self.queue(self.loop, loops=1) - def play(self, s, loops=0, maxtime=0, fade_ms=0): if loops: self.loop = s @@ -181,7 +195,8 @@ def Channel(n): sound_serial = 0 -sounds = { } +sounds = {} + class Sound(object): @@ -196,10 +211,10 @@ class Sound(object): self.serial = str(sound_serial) sound_serial += 1 - if isinstance(what, file): + if isinstance(what, file): # noqa F821 self.file = what else: - self.file = file(os.path.abspath(what), "rb") + self.file = file(os.path.abspath(what), "rb") # noqa F821 sounds[self.serial] = self @@ -214,7 +229,6 @@ class Sound(object): channel.play(self, loops=loops) return channel - def stop(self): for i in range(0, num_channels): if Channel(i).get_sound() is self: @@ -244,9 +258,11 @@ class Sound(object): def get_length(self): return 1.0 + music_channel = Channel(256) music_sound = None + class music(object): @staticmethod @@ -306,6 +322,3 @@ class music(object): @staticmethod def queue(filename): return music_channel.queue(Sound(filename)) - - - diff --git a/p4a/pythonforandroid/recipes/android/src/android/permissions.py b/p4a/pythonforandroid/recipes/android/src/android/permissions.py new file mode 100644 index 00000000..6c2d3843 --- /dev/null +++ b/p4a/pythonforandroid/recipes/android/src/android/permissions.py @@ -0,0 +1,438 @@ + +try: + from jnius import autoclass +except ImportError: + # To allow importing by build/manifest-creating code without + # pyjnius being present: + def autoclass(item): + raise RuntimeError("pyjnius not available") + + +class Permission: + ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER" + ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION" + ACCESS_LOCATION_EXTRA_COMMANDS = ( + "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" + ) + ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE" + ACCESS_NOTIFICATION_POLICY = ( + "android.permission.ACCESS_NOTIFICATION_POLICY" + ) + ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE" + ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL" + ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS" + BATTERY_STATS = "android.permission.BATTERY_STATS" + BIND_ACCESSIBILITY_SERVICE = ( + "android.permission.BIND_ACCESSIBILITY_SERVICE" + ) + BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE" + BIND_CARRIER_MESSAGING_SERVICE = ( # note: deprecated in api 23+ + "android.permission.BIND_CARRIER_MESSAGING_SERVICE" + ) + BIND_CARRIER_SERVICES = ( # replaces BIND_CARRIER_MESSAGING_SERVICE + "android.permission.BIND_CARRIER_SERVICES" + ) + BIND_CHOOSER_TARGET_SERVICE = ( + "android.permission.BIND_CHOOSER_TARGET_SERVICE" + ) + BIND_CONDITION_PROVIDER_SERVICE = ( + "android.permission.BIND_CONDITION_PROVIDER_SERVICE" + ) + BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN" + BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE" + BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE" + BIND_INPUT_METHOD = ( + "android.permission.BIND_INPUT_METHOD" + ) + BIND_MIDI_DEVICE_SERVICE = ( + "android.permission.BIND_MIDI_DEVICE_SERVICE" + ) + BIND_NFC_SERVICE = ( + "android.permission.BIND_NFC_SERVICE" + ) + BIND_NOTIFICATION_LISTENER_SERVICE = ( + "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" + ) + BIND_PRINT_SERVICE = ( + "android.permission.BIND_PRINT_SERVICE" + ) + BIND_QUICK_SETTINGS_TILE = ( + "android.permission.BIND_QUICK_SETTINGS_TILE" + ) + BIND_REMOTEVIEWS = ( + "android.permission.BIND_REMOTEVIEWS" + ) + BIND_SCREENING_SERVICE = ( + "android.permission.BIND_SCREENING_SERVICE" + ) + BIND_TELECOM_CONNECTION_SERVICE = ( + "android.permission.BIND_TELECOM_CONNECTION_SERVICE" + ) + BIND_TEXT_SERVICE = ( + "android.permission.BIND_TEXT_SERVICE" + ) + BIND_TV_INPUT = ( + "android.permission.BIND_TV_INPUT" + ) + BIND_VISUAL_VOICEMAIL_SERVICE = ( + "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE" + ) + BIND_VOICE_INTERACTION = ( + "android.permission.BIND_VOICE_INTERACTION" + ) + BIND_VPN_SERVICE = ( + "android.permission.BIND_VPN_SERVICE" + ) + BIND_VR_LISTENER_SERVICE = ( + "android.permission.BIND_VR_LISTENER_SERVICE" + ) + BIND_WALLPAPER = ( + "android.permission.BIND_WALLPAPER" + ) + BLUETOOTH = ( + "android.permission.BLUETOOTH" + ) + BLUETOOTH_ADMIN = ( + "android.permission.BLUETOOTH_ADMIN" + ) + BODY_SENSORS = ( + "android.permission.BODY_SENSORS" + ) + BROADCAST_PACKAGE_REMOVED = ( + "android.permission.BROADCAST_PACKAGE_REMOVED" + ) + BROADCAST_STICKY = ( + "android.permission.BROADCAST_STICKY" + ) + CALL_PHONE = ( + "android.permission.CALL_PHONE" + ) + CALL_PRIVILEGED = ( + "android.permission.CALL_PRIVILEGED" + ) + CAMERA = ( + "android.permission.CAMERA" + ) + CAPTURE_AUDIO_OUTPUT = ( + "android.permission.CAPTURE_AUDIO_OUTPUT" + ) + CAPTURE_SECURE_VIDEO_OUTPUT = ( + "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" + ) + CAPTURE_VIDEO_OUTPUT = ( + "android.permission.CAPTURE_VIDEO_OUTPUT" + ) + CHANGE_COMPONENT_ENABLED_STATE = ( + "android.permission.CHANGE_COMPONENT_ENABLED_STATE" + ) + CHANGE_CONFIGURATION = ( + "android.permission.CHANGE_CONFIGURATION" + ) + CHANGE_NETWORK_STATE = ( + "android.permission.CHANGE_NETWORK_STATE" + ) + CHANGE_WIFI_MULTICAST_STATE = ( + "android.permission.CHANGE_WIFI_MULTICAST_STATE" + ) + CHANGE_WIFI_STATE = ( + "android.permission.CHANGE_WIFI_STATE" + ) + CLEAR_APP_CACHE = ( + "android.permission.CLEAR_APP_CACHE" + ) + CONTROL_LOCATION_UPDATES = ( + "android.permission.CONTROL_LOCATION_UPDATES" + ) + DELETE_CACHE_FILES = ( + "android.permission.DELETE_CACHE_FILES" + ) + DELETE_PACKAGES = ( + "android.permission.DELETE_PACKAGES" + ) + DIAGNOSTIC = ( + "android.permission.DIAGNOSTIC" + ) + DISABLE_KEYGUARD = ( + "android.permission.DISABLE_KEYGUARD" + ) + DUMP = ( + "android.permission.DUMP" + ) + EXPAND_STATUS_BAR = ( + "android.permission.EXPAND_STATUS_BAR" + ) + FACTORY_TEST = ( + "android.permission.FACTORY_TEST" + ) + FOREGROUND_SERVICE = ( + "android.permission.FOREGROUND_SERVICE" + ) + GET_ACCOUNTS = ( + "android.permission.GET_ACCOUNTS" + ) + GET_ACCOUNTS_PRIVILEGED = ( + "android.permission.GET_ACCOUNTS_PRIVILEGED" + ) + GET_PACKAGE_SIZE = ( + "android.permission.GET_PACKAGE_SIZE" + ) + GET_TASKS = ( + "android.permission.GET_TASKS" + ) + GLOBAL_SEARCH = ( + "android.permission.GLOBAL_SEARCH" + ) + INSTALL_LOCATION_PROVIDER = ( + "android.permission.INSTALL_LOCATION_PROVIDER" + ) + INSTALL_PACKAGES = ( + "android.permission.INSTALL_PACKAGES" + ) + INSTALL_SHORTCUT = ( + "com.android.launcher.permission.INSTALL_SHORTCUT" + ) + INSTANT_APP_FOREGROUND_SERVICE = ( + "android.permission.INSTANT_APP_FOREGROUND_SERVICE" + ) + INTERNET = ( + "android.permission.INTERNET" + ) + KILL_BACKGROUND_PROCESSES = ( + "android.permission.KILL_BACKGROUND_PROCESSES" + ) + LOCATION_HARDWARE = ( + "android.permission.LOCATION_HARDWARE" + ) + MANAGE_DOCUMENTS = ( + "android.permission.MANAGE_DOCUMENTS" + ) + MANAGE_OWN_CALLS = ( + "android.permission.MANAGE_OWN_CALLS" + ) + MASTER_CLEAR = ( + "android.permission.MASTER_CLEAR" + ) + MEDIA_CONTENT_CONTROL = ( + "android.permission.MEDIA_CONTENT_CONTROL" + ) + MODIFY_AUDIO_SETTINGS = ( + "android.permission.MODIFY_AUDIO_SETTINGS" + ) + MODIFY_PHONE_STATE = ( + "android.permission.MODIFY_PHONE_STATE" + ) + MOUNT_FORMAT_FILESYSTEMS = ( + "android.permission.MOUNT_FORMAT_FILESYSTEMS" + ) + MOUNT_UNMOUNT_FILESYSTEMS = ( + "android.permission.MOUNT_UNMOUNT_FILESYSTEMS" + ) + NFC = ( + "android.permission.NFC" + ) + NFC_TRANSACTION_EVENT = ( + "android.permission.NFC_TRANSACTION_EVENT" + ) + PACKAGE_USAGE_STATS = ( + "android.permission.PACKAGE_USAGE_STATS" + ) + PERSISTENT_ACTIVITY = ( + "android.permission.PERSISTENT_ACTIVITY" + ) + PROCESS_OUTGOING_CALLS = ( + "android.permission.PROCESS_OUTGOING_CALLS" + ) + READ_CALENDAR = ( + "android.permission.READ_CALENDAR" + ) + READ_CALL_LOG = ( + "android.permission.READ_CALL_LOG" + ) + READ_CONTACTS = ( + "android.permission.READ_CONTACTS" + ) + READ_EXTERNAL_STORAGE = ( + "android.permission.READ_EXTERNAL_STORAGE" + ) + READ_FRAME_BUFFER = ( + "android.permission.READ_FRAME_BUFFER" + ) + READ_INPUT_STATE = ( + "android.permission.READ_INPUT_STATE" + ) + READ_LOGS = ( + "android.permission.READ_LOGS" + ) + READ_PHONE_NUMBERS = ( + "android.permission.READ_PHONE_NUMBERS" + ) + READ_PHONE_STATE = ( + "android.permission.READ_PHONE_STATE" + ) + READ_SMS = ( + "android.permission.READ_SMS" + ) + READ_SYNC_SETTINGS = ( + "android.permission.READ_SYNC_SETTINGS" + ) + READ_SYNC_STATS = ( + "android.permission.READ_SYNC_STATS" + ) + READ_VOICEMAIL = ( + "com.android.voicemail.permission.READ_VOICEMAIL" + ) + REBOOT = ( + "android.permission.REBOOT" + ) + RECEIVE_BOOT_COMPLETED = ( + "android.permission.RECEIVE_BOOT_COMPLETED" + ) + RECEIVE_MMS = ( + "android.permission.RECEIVE_MMS" + ) + RECEIVE_SMS = ( + "android.permission.RECEIVE_SMS" + ) + RECEIVE_WAP_PUSH = ( + "android.permission.RECEIVE_WAP_PUSH" + ) + RECORD_AUDIO = ( + "android.permission.RECORD_AUDIO" + ) + REORDER_TASKS = ( + "android.permission.REORDER_TASKS" + ) + REQUEST_COMPANION_RUN_IN_BACKGROUND = ( + "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" + ) + REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = ( + "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" + ) + REQUEST_DELETE_PACKAGES = ( + "android.permission.REQUEST_DELETE_PACKAGES" + ) + REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = ( + "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" + ) + REQUEST_INSTALL_PACKAGES = ( + "android.permission.REQUEST_INSTALL_PACKAGES" + ) + RESTART_PACKAGES = ( + "android.permission.RESTART_PACKAGES" + ) + SEND_RESPOND_VIA_MESSAGE = ( + "android.permission.SEND_RESPOND_VIA_MESSAGE" + ) + SEND_SMS = ( + "android.permission.SEND_SMS" + ) + SET_ALARM = ( + "com.android.alarm.permission.SET_ALARM" + ) + SET_ALWAYS_FINISH = ( + "android.permission.SET_ALWAYS_FINISH" + ) + SET_ANIMATION_SCALE = ( + "android.permission.SET_ANIMATION_SCALE" + ) + SET_DEBUG_APP = ( + "android.permission.SET_DEBUG_APP" + ) + SET_PREFERRED_APPLICATIONS = ( + "android.permission.SET_PREFERRED_APPLICATIONS" + ) + SET_PROCESS_LIMIT = ( + "android.permission.SET_PROCESS_LIMIT" + ) + SET_TIME = ( + "android.permission.SET_TIME" + ) + SET_TIME_ZONE = ( + "android.permission.SET_TIME_ZONE" + ) + SET_WALLPAPER = ( + "android.permission.SET_WALLPAPER" + ) + SET_WALLPAPER_HINTS = ( + "android.permission.SET_WALLPAPER_HINTS" + ) + SIGNAL_PERSISTENT_PROCESSES = ( + "android.permission.SIGNAL_PERSISTENT_PROCESSES" + ) + STATUS_BAR = ( + "android.permission.STATUS_BAR" + ) + SYSTEM_ALERT_WINDOW = ( + "android.permission.SYSTEM_ALERT_WINDOW" + ) + TRANSMIT_IR = ( + "android.permission.TRANSMIT_IR" + ) + UNINSTALL_SHORTCUT = ( + "com.android.launcher.permission.UNINSTALL_SHORTCUT" + ) + UPDATE_DEVICE_STATS = ( + "android.permission.UPDATE_DEVICE_STATS" + ) + USE_BIOMETRIC = ( + "android.permission.USE_BIOMETRIC" + ) + USE_FINGERPRINT = ( + "android.permission.USE_FINGERPRINT" + ) + USE_SIP = ( + "android.permission.USE_SIP" + ) + VIBRATE = ( + "android.permission.VIBRATE" + ) + WAKE_LOCK = ( + "android.permission.WAKE_LOCK" + ) + WRITE_APN_SETTINGS = ( + "android.permission.WRITE_APN_SETTINGS" + ) + WRITE_CALENDAR = ( + "android.permission.WRITE_CALENDAR" + ) + WRITE_CALL_LOG = ( + "android.permission.WRITE_CALL_LOG" + ) + WRITE_CONTACTS = ( + "android.permission.WRITE_CONTACTS" + ) + WRITE_EXTERNAL_STORAGE = ( + "android.permission.WRITE_EXTERNAL_STORAGE" + ) + WRITE_GSERVICES = ( + "android.permission.WRITE_GSERVICES" + ) + WRITE_SECURE_SETTINGS = ( + "android.permission.WRITE_SECURE_SETTINGS" + ) + WRITE_SETTINGS = ( + "android.permission.WRITE_SETTINGS" + ) + WRITE_SYNC_SETTINGS = ( + "android.permission.WRITE_SYNC_SETTINGS" + ) + WRITE_VOICEMAIL = ( + "com.android.voicemail.permission.WRITE_VOICEMAIL" + ) + + +def request_permissions(permissions): + python_activity = autoclass('org.kivy.android.PythonActivity') + python_activity.requestPermissions(permissions) + + +def request_permission(permission): + request_permissions([permission]) + + +def check_permission(permission): + python_activity = autoclass('org.kivy.android.PythonActivity') + result = bool(python_activity.checkCurrentPermission( + permission + "" + )) + return result diff --git a/p4a/pythonforandroid/recipes/android/src/android/runnable.py b/p4a/pythonforandroid/recipes/android/src/android/runnable.py index 564d83b0..8d2d1161 100644 --- a/p4a/pythonforandroid/recipes/android/src/android/runnable.py +++ b/p4a/pythonforandroid/recipes/android/src/android/runnable.py @@ -33,12 +33,13 @@ class Runnable(PythonJavaClass): def run(self): try: self.func(*self.args, **self.kwargs) - except: + except: # noqa E722 import traceback traceback.print_exc() Runnable.__runnables__.remove(self) + def run_on_ui_thread(f): '''Decorator to create automatically a :class:`Runnable` object with the function. The function will be delayed and call into the Activity thread. diff --git a/p4a/pythonforandroid/recipes/android/src/setup.py b/p4a/pythonforandroid/recipes/android/src/setup.py index 47ef6a94..2e95a862 100755 --- a/p4a/pythonforandroid/recipes/android/src/setup.py +++ b/p4a/pythonforandroid/recipes/android/src/setup.py @@ -6,7 +6,7 @@ lib_dict = { 'pygame': ['sdl'], 'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf'] } -sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else [] +sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], []) renpy_sound = Extension('android._android_sound', ['android/_android_sound.c', 'android/_android_sound_jni.c', ], diff --git a/p4a/pythonforandroid/recipes/apsw/__init__.py b/p4a/pythonforandroid/recipes/apsw/__init__.py index 92bdd7df..6098e4b9 100644 --- a/p4a/pythonforandroid/recipes/apsw/__init__.py +++ b/p4a/pythonforandroid/recipes/apsw/__init__.py @@ -1,11 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe, shprint, shutil, current_directory -from os.path import join, exists +from pythonforandroid.recipe import PythonRecipe +from pythonforandroid.toolchain import current_directory, shprint import sh + class ApswRecipe(PythonRecipe): version = '3.15.0-r1' url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz' - depends = ['sqlite3', 'hostpython2', 'python2', 'setuptools'] + depends = ['sqlite3', ('python2', 'python3'), 'setuptools'] call_hostpython_via_targetpython = False site_packages_name = 'apsw' @@ -17,21 +18,17 @@ class ApswRecipe(PythonRecipe): shprint(hostpython, 'setup.py', 'build_ext', - '--enable=fts4' - , _env=env) + '--enable=fts4', _env=env) # Install python bindings super(ApswRecipe, self).build_arch(arch) def get_recipe_env(self, arch): env = super(ApswRecipe, self).get_recipe_env(arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \ - ' -I' + self.get_recipe('sqlite3', self.ctx).get_build_dir(arch.arch) - # Set linker to use the correct gcc - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -lpython2.7' + \ - ' -lsqlite3' + sqlite_recipe = self.get_recipe('sqlite3', self.ctx) + env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch) + env['LDFLAGS'] += ' -L' + sqlite_recipe.get_lib_dir(arch) + env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3' return env + recipe = ApswRecipe() diff --git a/p4a/pythonforandroid/recipes/atom/__init__.py b/p4a/pythonforandroid/recipes/atom/__init__.py index 57d363be..51923d54 100644 --- a/p4a/pythonforandroid/recipes/atom/__init__.py +++ b/p4a/pythonforandroid/recipes/atom/__init__.py @@ -1,9 +1,11 @@ from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe + class AtomRecipe(CppCompiledComponentsPythonRecipe): site_packages_name = 'atom' version = '0.3.10' url = 'https://github.com/nucleic/atom/archive/master.zip' - depends = ['python2','setuptools'] - + depends = ['setuptools'] + + recipe = AtomRecipe() diff --git a/p4a/pythonforandroid/recipes/audiostream/__init__.py b/p4a/pythonforandroid/recipes/audiostream/__init__.py index af38353b..4197abd0 100644 --- a/p4a/pythonforandroid/recipes/audiostream/__init__.py +++ b/p4a/pythonforandroid/recipes/audiostream/__init__.py @@ -1,36 +1,32 @@ -from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, info -import sh -import glob -from os.path import join, exists +from pythonforandroid.recipe import CythonRecipe +from os.path import join class AudiostreamRecipe(CythonRecipe): - version = 'master' + version = 'master' url = 'https://github.com/kivy/audiostream/archive/{version}.zip' name = 'audiostream' - depends = ['python2', ('sdl', 'sdl2'), 'pyjnius'] + depends = [('python2', 'python3'), ('sdl', 'sdl2'), 'pyjnius'] def get_recipe_env(self, arch): + env = super(AudiostreamRecipe, self).get_recipe_env(arch) if 'sdl' in self.ctx.recipe_build_order: sdl_include = 'sdl' sdl_mixer_include = 'sdl_mixer' elif 'sdl2' in self.ctx.recipe_build_order: - sdl_include = 'SDL' + sdl_include = 'SDL2' sdl_mixer_include = 'SDL2_mixer' - - #note: audiostream library is not yet able to judge whether it is being used with sdl or with sdl2. - #this causes linking to fail against SDL2 (compiling against SDL2 works) - #need to find a way to fix this in audiostream's setup.py - raise RuntimeError('Audiostream library is not yet able to configure itself to link against SDL2. Patch on audiostream library needed - any help much appreciated!') + env['USE_SDL2'] = 'True' + env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') - env = super(AudiostreamRecipe, self).get_recipe_env(arch) env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format( - jni_path = join(self.ctx.bootstrap.build_dir, 'jni'), - sdl_include = sdl_include, - sdl_mixer_include = sdl_mixer_include) + jni_path=join(self.ctx.bootstrap.build_dir, 'jni'), + sdl_include=sdl_include, + sdl_mixer_include=sdl_mixer_include) + env['NDKPLATFORM'] = self.ctx.ndk_platform + env['LIBLINK'] = 'NOTNONE' # Hacky fix. Needed by audiostream setup.py return env - recipe = AudiostreamRecipe() diff --git a/p4a/pythonforandroid/recipes/babel/__init__.py b/p4a/pythonforandroid/recipes/babel/__init__.py index 818c973f..fc17f8e4 100644 --- a/p4a/pythonforandroid/recipes/babel/__init__.py +++ b/p4a/pythonforandroid/recipes/babel/__init__.py @@ -2,14 +2,14 @@ from pythonforandroid.recipe import PythonRecipe class BabelRecipe(PythonRecipe): - name = 'babel' - version = '2.1.1' - url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz' + name = 'babel' + version = '2.2.0' + url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools', 'pytz'] + depends = ['setuptools', 'pytz'] - call_hostpython_via_targetpython = False - install_in_hostpython = True + call_hostpython_via_targetpython = False + install_in_hostpython = True recipe = BabelRecipe() diff --git a/p4a/pythonforandroid/recipes/boost/__init__.py b/p4a/pythonforandroid/recipes/boost/__init__.py index 26afc2a0..53d93888 100644 --- a/p4a/pythonforandroid/recipes/boost/__init__.py +++ b/p4a/pythonforandroid/recipes/boost/__init__.py @@ -1,17 +1,45 @@ from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory from os.path import join, exists +from os import environ import sh """ This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build including python bindings """ + + class BoostRecipe(Recipe): - version = '1.60.0' - # Don't forget to change the URL when changing the version - url = 'http://downloads.sourceforge.net/project/boost/boost/{version}/boost_1_60_0.tar.bz2' - depends = ['python2'] - patches = ['disable-so-version.patch', 'use-android-libs.patch'] + # Todo: make recipe compatible with all p4a architectures + ''' + .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a + + .. versionchanged:: 0.6.0 + Rewrote recipe to support clang's build. The following changes has + been made: + + - Bumped version number to 1.68.0 + - Better version handling for url + - Added python 3 compatibility + - Default compiler for ndk's toolchain set to clang + - Python version will be detected via user-config.jam + - Changed stl's lib from ``gnustl_shared`` to ``c++_shared`` + ''' + version = '1.68.0' + url = 'http://downloads.sourceforge.net/project/boost/' \ + 'boost/{version}/boost_{version_underscore}.tar.bz2' + depends = [('python2', 'python3')] + patches = ['disable-so-version.patch', + 'use-android-libs.patch', + 'fix-android-issues.patch'] + + @property + def versioned_url(self): + if self.url is None: + return None + return self.url.format( + version=self.version, + version_underscore=self.version.replace('.', '_')) def should_build(self, arch): return not exists(join(self.get_build_dir(arch.arch), 'b2')) @@ -26,9 +54,11 @@ class BoostRecipe(Recipe): shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'), '--arch=' + env['ARCH'], '--platform=android-' + str(self.ctx.android_api), - '--toolchain=' + env['CROSSHOST'] + '-' + env['TOOLCHAIN_VERSION'], + '--toolchain=' + env['CROSSHOST'] + '-' + self.ctx.toolchain_version + ':-llvm', + '--use-llvm', + '--stl=libc++', '--install-dir=' + env['CROSSHOME'] - ) + ) # Set custom configuration shutil.copyfile(join(self.get_recipe_dir(), 'user-config.jam'), join(env['BOOST_BUILD_PATH'], 'user-config.jam')) @@ -36,32 +66,39 @@ class BoostRecipe(Recipe): def build_arch(self, arch): super(BoostRecipe, self).build_arch(arch) env = self.get_recipe_env(arch) + env['PYTHON_HOST'] = self.ctx.hostpython with current_directory(self.get_build_dir(arch.arch)): # Compile Boost.Build engine with this custom toolchain bash = sh.Command('bash') - shprint(bash, 'bootstrap.sh', - '--with-python=' + join(env['PYTHON_ROOT'], 'bin/python.host'), - '--with-python-version=2.7', - '--with-python-root=' + env['PYTHON_ROOT'] - ) # Do not pass env + shprint(bash, 'bootstrap.sh') # Do not pass env # Install app stl - shutil.copyfile(join(env['CROSSHOME'], env['CROSSHOST'], 'lib/libgnustl_shared.so'), - join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so')) + shutil.copyfile( + join(self.ctx.ndk_dir, 'sources/cxx-stl/llvm-libc++/libs/' + 'armeabi-v7a/libc++_shared.so'), + join(self.ctx.get_libs_dir(arch.arch), 'libc++_shared.so')) def select_build_arch(self, arch): return arch.arch.replace('eabi-v7a', '').replace('eabi', '') def get_recipe_env(self, arch): - env = super(BoostRecipe, self).get_recipe_env(arch) + # We don't use the normal env because we + # are building with a standalone toolchain + env = environ.copy() + env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() + + env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch) + env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch) + env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3] + env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.major_minor_version_string + if 'python3' in self.ctx.python_recipe.name: + env['PYTHON_LINK_VERSION'] += 'm' + env['ARCH'] = self.select_build_arch(arch) - env['ANDROIDAPI'] = str(self.ctx.android_api) env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi' env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain') - env['TOOLCHAIN_PREFIX'] = join(env['CROSSHOME'], 'bin', env['CROSSHOST']) return env -recipe = BoostRecipe() \ No newline at end of file +recipe = BoostRecipe() diff --git a/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch b/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch new file mode 100644 index 00000000..54134800 --- /dev/null +++ b/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch @@ -0,0 +1,68 @@ +diff -u -r boost_1_68_0.orig/boost/config/user.hpp boost_1_68_0/boost/config/user.hpp +--- boost_1_68_0.orig/boost/config/user.hpp 2018-08-01 22:50:46.000000000 +0200 ++++ boost_1_68_0/boost/config/user.hpp 2018-08-27 15:43:38.000000000 +0200 +@@ -13,6 +13,12 @@ + // configuration policy: + // + ++// Android defines ++// There is problem with std::atomic on android (and some other platforms). ++// See this link for more info: ++// https://code.google.com/p/android/issues/detail?id=42735#makechanges ++#define BOOST_ASIO_DISABLE_STD_ATOMIC 1 ++ + // define this to locate a compiler config file: + // #define BOOST_COMPILER_CONFIG + +diff -u -r boost_1_68_0.orig/boost/asio/detail/config.hpp boost_1_68_0/boost/asio/detail/config.hpp +--- boost_1_68_0.orig/boost/asio/detail/config.hpp 2018-08-01 22:50:46.000000000 +0200 ++++ boost_1_68_0/boost/asio/detail/config.hpp 2018-09-19 12:39:56.000000000 +0200 +@@ -804,7 +804,11 @@ + # if defined(__clang__) + # if (__cplusplus >= 201402) + # if __has_include() +-# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1 ++# if __clang_major__ >= 7 ++# undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW ++# else ++# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1 ++# endif // __clang_major__ >= 7 + # endif // __has_include() + # endif // (__cplusplus >= 201402) + # endif // defined(__clang__) +diff -u -r boost_1_68_0.orig/boost/system/error_code.hpp boost_1_68_0/boost/system/error_code.hpp +--- boost_1_68_0.orig/boost/system/error_code.hpp 2018-08-01 22:50:53.000000000 +0200 ++++ boost_1_68_0/boost/system/error_code.hpp 2018-08-27 15:44:29.000000000 +0200 +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +diff -u -r boost_1_68_0.orig/libs/filesystem/src/operations.cpp boost_1_68_0/libs/filesystem/src/operations.cpp +--- boost_1_68_0.orig/libs/filesystem/src/operations.cpp 2018-08-01 22:50:47.000000000 +0200 ++++ boost_1_68_0/libs/filesystem/src/operations.cpp 2018-08-27 15:47:15.000000000 +0200 +@@ -232,6 +232,21 @@ + + # if defined(BOOST_POSIX_API) + ++# if defined(__ANDROID__) ++# define truncate libboost_truncate_wrapper ++// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper ++static int libboost_truncate_wrapper(const char *path, off_t length) ++{ ++ int fd = open(path, O_WRONLY); ++ if (fd == -1) { ++ return -1; ++ } ++ int status = ftruncate(fd, length); ++ close(fd); ++ return status; ++} ++# endif ++ + typedef int err_t; + + // POSIX uses a 0 return to indicate success diff --git a/p4a/pythonforandroid/recipes/boost/user-config.jam b/p4a/pythonforandroid/recipes/boost/user-config.jam index 72643d8a..e50b50af 100644 --- a/p4a/pythonforandroid/recipes/boost/user-config.jam +++ b/p4a/pythonforandroid/recipes/boost/user-config.jam @@ -1,28 +1,61 @@ import os ; -local ANDROIDNDK = [ os.environ ANDROIDNDK ] ; -local ANDROIDAPI = [ os.environ ANDROIDAPI ] ; -local TOOLCHAIN_VERSION = [ os.environ TOOLCHAIN_VERSION ] ; -local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ; local ARCH = [ os.environ ARCH ] ; +local CROSSHOME = [ os.environ CROSSHOME ] ; +local PYTHON_HOST = [ os.environ PYTHON_HOST ] ; local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ; +local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ; +local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ; +local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ; -using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ : +using clang : $(ARCH) : $(CROSSHOME)/bin/arm-linux-androideabi-clang++ : +$(CROSSHOME)/bin/arm-linux-androideabi-ar +$(CROSSHOME)/sysroot $(ARCH) -$(TOOLCHAIN_PREFIX)-ar --DBOOST_SP_USE_PTHREADS --DBOOST_AC_USE_PTHREADS --DBOOST_SP_USE_PTHREADS --DBOOST_AC_USE_PTHREADS --frtti --fexceptions --I$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)/usr/include --I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include --I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include --I$(PYTHON_ROOT)/include/python2.7 ---sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH) --L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH) --L$(PYTHON_ROOT)/lib --lgnustl_shared --lpython2.7 +-fexceptions +-frtti +-fpic +-ffunction-sections +-funwind-tables +-march=armv7-a +-msoft-float +-mfpu=neon +-mthumb +-march=armv7-a +-Wl,--fix-cortex-a8 +-Os +-fomit-frame-pointer +-fno-strict-aliasing +-DANDROID +-D__ANDROID__ +-DANDROID_TOOLCHAIN=clang +-DANDROID_ABI=armv7-a +-DANDROID_STL=c++_shared +-DBOOST_ALL_NO_LIB +#-DNDEBUG +-O2 +-g +-fvisibility=hidden +-fvisibility-inlines-hidden +-fdata-sections +-D__arm__ +-D_REENTRANT +-D_GLIBCXX__PTHREADS +-Wno-long-long +-Wno-missing-field-initializers +-Wno-unused-variable +-Wl,-z,relro +-Wl,-z,now +-lc++_shared +-L$(PYTHON_ROOT) +-lpython$(PYTHON_LINK_VERSION) +-Wl,-O1 +-Wl,-Bsymbolic-functions ; + +using python : $(PYTHON_MAJOR_MINOR) + : $(PYTHON_host) + : $(PYTHON_ROOT) $(PYTHON_INCLUDE) + : $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so + : #BOOST_ALL_DYN_LINK +; \ No newline at end of file diff --git a/p4a/pythonforandroid/recipes/brokenrecipe/__init__.py b/p4a/pythonforandroid/recipes/brokenrecipe/__init__.py index b617074b..48e266b3 100644 --- a/p4a/pythonforandroid/recipes/brokenrecipe/__init__.py +++ b/p4a/pythonforandroid/recipes/brokenrecipe/__init__.py @@ -1,5 +1,6 @@ from pythonforandroid.toolchain import Recipe + class BrokenRecipe(Recipe): def __init__(self): print('This is a broken recipe, not a real one!') diff --git a/p4a/pythonforandroid/recipes/cdecimal/__init__.py b/p4a/pythonforandroid/recipes/cdecimal/__init__.py index e0818594..94929c78 100644 --- a/p4a/pythonforandroid/recipes/cdecimal/__init__.py +++ b/p4a/pythonforandroid/recipes/cdecimal/__init__.py @@ -1,5 +1,4 @@ - -from pythonforandroid.toolchain import CompiledComponentsPythonRecipe +from pythonforandroid.recipe import CompiledComponentsPythonRecipe from pythonforandroid.patching import is_darwin @@ -8,7 +7,7 @@ class CdecimalRecipe(CompiledComponentsPythonRecipe): version = '2.3' url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz' - depends = ['python2'] + depends = [] patches = ['locale.patch', 'cross-compile.patch'] diff --git a/p4a/pythonforandroid/recipes/cffi/__init__.py b/p4a/pythonforandroid/recipes/cffi/__init__.py index 450c32a5..50458e55 100644 --- a/p4a/pythonforandroid/recipes/cffi/__init__.py +++ b/p4a/pythonforandroid/recipes/cffi/__init__.py @@ -1,30 +1,53 @@ +import os from pythonforandroid.recipe import CompiledComponentsPythonRecipe class CffiRecipe(CompiledComponentsPythonRecipe): - name = 'cffi' - version = '1.4.2' - url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz' + """ + Extra system dependencies: autoconf, automake and libtool. + """ + name = 'cffi' + version = '1.11.5' + url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools', 'pycparser', 'libffi'] + depends = ['setuptools', 'pycparser', 'libffi'] - patches = ['disable-pkg-config.patch'] + patches = ['disable-pkg-config.patch'] - # call_hostpython_via_targetpython = False - install_in_hostpython = True + # call_hostpython_via_targetpython = False + install_in_hostpython = True - def get_recipe_env(self, arch=None): - env = super(CffiRecipe, self).get_recipe_env(arch) - libffi = self.get_recipe('libffi', self.ctx) - includes = libffi.get_include_dirs(arch) - env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes) - env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' + - self.ctx.get_libs_dir(arch.arch)) - env['PYTHONPATH'] = ':'.join([ - self.ctx.get_site_packages_dir(), - env['BUILDLIB_PATH'], - ]) - return env + def get_hostrecipe_env(self, arch=None): + # fixes missing ffi.h on some host systems (e.g. gentoo) + env = super(CffiRecipe, self).get_hostrecipe_env(arch) + libffi = self.get_recipe('libffi', self.ctx) + includes = libffi.get_include_dirs(arch) + env['FFI_INC'] = ",".join(includes) + return env + + def get_recipe_env(self, arch=None): + env = super(CffiRecipe, self).get_recipe_env(arch) + libffi = self.get_recipe('libffi', self.ctx) + includes = libffi.get_include_dirs(arch) + env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes) + env['CFLAGS'] += ' -I{}'.format(self.ctx.python_recipe.include_root(arch.arch)) + env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' + + self.ctx.get_libs_dir(arch.arch)) + env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)) + # required for libc and libdl + ndk_dir = self.ctx.ndk_platform + ndk_lib_dir = os.path.join(ndk_dir, 'usr', 'lib') + env['LDFLAGS'] += ' -L{}'.format(ndk_lib_dir) + env['LDFLAGS'] += " --sysroot={}".format(self.ctx.ndk_platform) + env['PYTHONPATH'] = ':'.join([ + self.ctx.get_site_packages_dir(), + env['BUILDLIB_PATH'], + ]) + env['LDFLAGS'] += ' -L{}'.format(self.ctx.python_recipe.link_root(arch.arch)) + env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.major_minor_version_string) + if 'python3' in self.ctx.python_recipe.name: + env['LDFLAGS'] += 'm' + return env recipe = CffiRecipe() diff --git a/p4a/pythonforandroid/recipes/cffi/disable-pkg-config.patch b/p4a/pythonforandroid/recipes/cffi/disable-pkg-config.patch index 56346bb7..cf2abd5b 100644 --- a/p4a/pythonforandroid/recipes/cffi/disable-pkg-config.patch +++ b/p4a/pythonforandroid/recipes/cffi/disable-pkg-config.patch @@ -1,17 +1,18 @@ -diff -Naur cffi-1.4.2/setup.py b/setup.py ---- cffi-1.4.2/setup.py 2015-12-21 12:09:47.000000000 -0600 -+++ b/setup.py 2015-12-23 10:20:40.590622524 -0600 -@@ -5,8 +5,7 @@ +diff --git a/setup.py b/setup.py +index c1db368..57311c3 100644 +--- a/setup.py ++++ b/setup.py +@@ -5,8 +5,7 @@ import errno sources = ['c/_cffi_backend.c'] libraries = ['ffi'] -include_dirs = ['/usr/include/ffi', - '/usr/include/libffi'] # may be changed by pkg-config -+include_dirs = [] ++include_dirs = os.environ['FFI_INC'].split(",") if 'FFI_INC' in os.environ else [] define_macros = [] library_dirs = [] extra_compile_args = [] -@@ -67,14 +66,7 @@ +@@ -67,14 +66,7 @@ def ask_supports_thread(): sys.stderr.write("The above error message can be safely ignored\n") def use_pkg_config(): diff --git a/p4a/pythonforandroid/recipes/cherrypy/__init__.py b/p4a/pythonforandroid/recipes/cherrypy/__init__.py index 74ed3dbb..6d3844b1 100644 --- a/p4a/pythonforandroid/recipes/cherrypy/__init__.py +++ b/p4a/pythonforandroid/recipes/cherrypy/__init__.py @@ -1,10 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe + class CherryPyRecipe(PythonRecipe): version = '5.1.0' url = 'https://bitbucket.org/cherrypy/cherrypy/get/{version}.tar.gz' - depends = ['hostpython2', 'setuptools'] + depends = ['setuptools'] site_packages_name = 'cherrypy' call_hostpython_via_targetpython = False + recipe = CherryPyRecipe() diff --git a/p4a/pythonforandroid/recipes/coverage/__init__.py b/p4a/pythonforandroid/recipes/coverage/__init__.py index a37358be..95f08f1f 100644 --- a/p4a/pythonforandroid/recipes/coverage/__init__.py +++ b/p4a/pythonforandroid/recipes/coverage/__init__.py @@ -1,4 +1,4 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class CoverageRecipe(PythonRecipe): @@ -7,7 +7,7 @@ class CoverageRecipe(PythonRecipe): url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz' - depends = ['hostpython2', 'setuptools'] + depends = [('hostpython2', 'hostpython3'), 'setuptools'] patches = ['fallback-utf8.patch'] diff --git a/p4a/pythonforandroid/recipes/cryptography/__init__.py b/p4a/pythonforandroid/recipes/cryptography/__init__.py index 64f79df9..1b7babaf 100644 --- a/p4a/pythonforandroid/recipes/cryptography/__init__.py +++ b/p4a/pythonforandroid/recipes/cryptography/__init__.py @@ -1,27 +1,23 @@ -from pythonforandroid.recipe import CompiledComponentsPythonRecipe -from os.path import dirname, join +from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe + class CryptographyRecipe(CompiledComponentsPythonRecipe): name = 'cryptography' - version = '1.4' + version = '2.6.1' url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'openssl', 'idna', 'pyasn1', 'six', 'setuptools', 'ipaddress', 'cffi'] + depends = ['openssl', 'idna', 'asn1crypto', 'six', 'setuptools', + 'enum34', 'ipaddress', 'cffi'] call_hostpython_via_targetpython = False def get_recipe_env(self, arch): env = super(CryptographyRecipe, self).get_recipe_env(arch) - r = self.get_recipe('openssl', self.ctx) - openssl_dir = r.get_build_dir(arch.arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \ - ' -I' + join(openssl_dir, 'include') - # Set linker to use the correct gcc - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -L' + openssl_dir + \ - ' -lpython2.7' + \ - ' -lssl' + r.version + \ - ' -lcrypto' + r.version + + openssl_recipe = Recipe.get_recipe('openssl', self.ctx) + env['CFLAGS'] += openssl_recipe.include_flags(arch) + env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch) + env['LIBS'] = openssl_recipe.link_libs_flags() + return env + recipe = CryptographyRecipe() diff --git a/p4a/pythonforandroid/recipes/cymunk/__init__.py b/p4a/pythonforandroid/recipes/cymunk/__init__.py index c9733e3e..96d41697 100644 --- a/p4a/pythonforandroid/recipes/cymunk/__init__.py +++ b/p4a/pythonforandroid/recipes/cymunk/__init__.py @@ -1,4 +1,4 @@ -from pythonforandroid.toolchain import CythonRecipe +from pythonforandroid.recipe import CythonRecipe class CymunkRecipe(CythonRecipe): @@ -6,7 +6,7 @@ class CymunkRecipe(CythonRecipe): url = 'https://github.com/tito/cymunk/archive/{version}.zip' name = 'cymunk' - depends = [('python2', 'python3crystax')] + depends = [('python2', 'python3crystax', 'python3')] recipe = CymunkRecipe() diff --git a/p4a/pythonforandroid/recipes/dateutil/__init__.py b/p4a/pythonforandroid/recipes/dateutil/__init__.py index 18e65047..3367f8d1 100644 --- a/p4a/pythonforandroid/recipes/dateutil/__init__.py +++ b/p4a/pythonforandroid/recipes/dateutil/__init__.py @@ -2,13 +2,13 @@ from pythonforandroid.recipe import PythonRecipe class DateutilRecipe(PythonRecipe): - name = 'dateutil' - version = '2.6.0' - url = 'https://pypi.python.org/packages/51/fc/39a3fbde6864942e8bb24c93663734b74e281b984d1b8c4f95d64b0c21f6/python-dateutil-2.6.0.tar.gz' + name = 'dateutil' + version = '2.6.0' + url = 'https://pypi.python.org/packages/51/fc/39a3fbde6864942e8bb24c93663734b74e281b984d1b8c4f95d64b0c21f6/python-dateutil-2.6.0.tar.gz' - depends = ['python2', "setuptools"] - call_hostpython_via_targetpython = False - install_in_hostpython = True + depends = ["setuptools"] + call_hostpython_via_targetpython = False + install_in_hostpython = True recipe = DateutilRecipe() diff --git a/p4a/pythonforandroid/recipes/decorator/__init__.py b/p4a/pythonforandroid/recipes/decorator/__init__.py index 6a20b31e..e1001dd6 100644 --- a/p4a/pythonforandroid/recipes/decorator/__init__.py +++ b/p4a/pythonforandroid/recipes/decorator/__init__.py @@ -1,10 +1,13 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe + class DecoratorPyRecipe(PythonRecipe): - version = '4.0.9' + version = '4.2.1' url = 'https://pypi.python.org/packages/source/d/decorator/decorator-{version}.tar.gz' - depends = ['hostpython2', 'setuptools'] + url = 'https://github.com/micheles/decorator/archive/{version}.tar.gz' + depends = ['setuptools'] site_packages_name = 'decorator' call_hostpython_via_targetpython = False + recipe = DecoratorPyRecipe() diff --git a/p4a/pythonforandroid/recipes/enaml/__init__.py b/p4a/pythonforandroid/recipes/enaml/__init__.py index 89c07008..d2335206 100644 --- a/p4a/pythonforandroid/recipes/enaml/__init__.py +++ b/p4a/pythonforandroid/recipes/enaml/__init__.py @@ -1,10 +1,12 @@ from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe + class EnamlRecipe(CppCompiledComponentsPythonRecipe): site_packages_name = 'enaml' version = '0.9.8' - url = 'https://github.com/nucleic/enaml/archive/master.zip' - patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency - depends = ['python2','setuptools','atom','kiwisolver'] + url = 'https://github.com/nucleic/enaml/archive/{version}.zip' + patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency + depends = ['setuptools', 'atom', 'kiwisolver'] + recipe = EnamlRecipe() diff --git a/p4a/pythonforandroid/recipes/enum34/__init__.py b/p4a/pythonforandroid/recipes/enum34/__init__.py index ba9acfc7..9a438c55 100644 --- a/p4a/pythonforandroid/recipes/enum34/__init__.py +++ b/p4a/pythonforandroid/recipes/enum34/__init__.py @@ -1,10 +1,22 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe + class Enum34Recipe(PythonRecipe): - version = '1.1.3' + version = '1.1.6' url = 'https://pypi.python.org/packages/source/e/enum34/enum34-{version}.tar.gz' - depends = ['python2', 'setuptools'] + depends = ['setuptools'] site_packages_name = 'enum' call_hostpython_via_targetpython = False + def should_build(self, arch): + if 'python3' in self.ctx.python_recipe.name: + # Since python 3.6 the enum34 library is no longer compatible with + # the standard library and it will cause errors, so we disable it + # in favour of the internal module, but we still add python3 to + # attribute `depends` because otherwise we will not be able to + # build the cryptography recipe. + return False + return super(Enum34Recipe, self).should_build(arch) + + recipe = Enum34Recipe() diff --git a/p4a/pythonforandroid/recipes/ethash/__init__.py b/p4a/pythonforandroid/recipes/ethash/__init__.py index 403513d8..b65e10ad 100644 --- a/p4a/pythonforandroid/recipes/ethash/__init__.py +++ b/p4a/pythonforandroid/recipes/ethash/__init__.py @@ -5,7 +5,7 @@ class EthashRecipe(PythonRecipe): url = 'https://github.com/ethereum/ethash/archive/master.zip' - depends = ['python2', 'setuptools'] + depends = ['setuptools'] recipe = EthashRecipe() diff --git a/p4a/pythonforandroid/recipes/evdev/__init__.py b/p4a/pythonforandroid/recipes/evdev/__init__.py index b4921dd7..afd542e2 100644 --- a/p4a/pythonforandroid/recipes/evdev/__init__.py +++ b/p4a/pythonforandroid/recipes/evdev/__init__.py @@ -6,7 +6,7 @@ class EvdevRecipe(CompiledComponentsPythonRecipe): version = 'v0.4.7' url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip' - depends = [('python2', 'python3crystax')] + depends = [] build_cmd = 'build' diff --git a/p4a/pythonforandroid/recipes/feedparser/__init__.py b/p4a/pythonforandroid/recipes/feedparser/__init__.py index d030494a..cce88b9e 100644 --- a/p4a/pythonforandroid/recipes/feedparser/__init__.py +++ b/p4a/pythonforandroid/recipes/feedparser/__init__.py @@ -1,10 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe + class FeedparserPyRecipe(PythonRecipe): version = '5.2.1' url = 'https://github.com/kurtmckee/feedparser/archive/{version}.tar.gz' - depends = ['hostpython2', 'setuptools'] + depends = ['setuptools'] site_packages_name = 'feedparser' call_hostpython_via_targetpython = False + recipe = FeedparserPyRecipe() diff --git a/p4a/pythonforandroid/recipes/ffmpeg/__init__.py b/p4a/pythonforandroid/recipes/ffmpeg/__init__.py index 3ca7dac6..f8e3ec14 100644 --- a/p4a/pythonforandroid/recipes/ffmpeg/__init__.py +++ b/p4a/pythonforandroid/recipes/ffmpeg/__init__.py @@ -1,19 +1,14 @@ -from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM +from pythonforandroid.toolchain import Recipe, current_directory, shprint from os.path import exists, join, realpath -from os import uname -import glob import sh -import os -import shutil class FFMpegRecipe(Recipe): - version = '3.1.8' # 3.2+ works with bugs + version = '3.4.5' url = 'http://ffmpeg.org/releases/ffmpeg-{version}.tar.bz2' - md5sum = 'f25a0cdd7f731cfbd8c0f7842b0d15b9' depends = ['sdl2'] # Need this to build correct recipe order opts_depends = ['openssl', 'ffpyplayer_codecs'] - patches = ['patches/fix-libshine-configure.patch'] + patches = ['patches/configure.patch'] def should_build(self, arch): build_dir = self.get_build_dir(arch.arch) @@ -22,7 +17,7 @@ class FFMpegRecipe(Recipe): def prebuild_arch(self, arch): self.apply_patches(arch) - def get_recipe_env(self,arch): + def get_recipe_env(self, arch): env = super(FFMpegRecipe, self).get_recipe_env(arch) env['NDK'] = self.ctx.ndk_dir return env @@ -37,7 +32,7 @@ class FFMpegRecipe(Recipe): if 'openssl' in self.ctx.recipe_build_order: flags += [ - '--enable-openssl', + '--enable-openssl', '--enable-nonfree', '--enable-protocol=https,tls_openssl', ] @@ -45,7 +40,7 @@ class FFMpegRecipe(Recipe): cflags += ['-I' + build_dir + '/include/'] ldflags += ['-L' + build_dir] - if 'ffpyplayer_codecs' in self.ctx.recipe_build_order: + if 'ffpyplayer_codecs' in self.ctx.recipe_build_order: # libx264 flags += ['--enable-libx264'] build_dir = Recipe.get_recipe('libx264', self.ctx).get_build_dir(arch.arch) @@ -69,16 +64,10 @@ class FFMpegRecipe(Recipe): else: # Enable codecs only for .mp4: flags += [ - '--enable-parser=h264,aac', - '--enable-decoder=h263,h264,aac', - ] - - # disable some unused algo - # note: "golomb" are the one used in our video test, so don't use --disable-golomb - # note: and for aac decoding: "rdft", "mdct", and "fft" are needed - flags += [ - '--disable-dxva2 --disable-vdpau --disable-vaapi', - '--disable-dct', + '--enable-parser=aac,ac3,h261,h264,mpegaudio,mpeg4video,mpegvideo,vc1', + '--enable-decoder=aac,h264,mpeg4,mpegvideo', + '--enable-muxer=h264,mov,mp4,mpeg2video', + '--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1', ] # needed to prevent _ffmpeg.so: version node not found for symbol av_init_packet@LIBAVFORMAT_52 @@ -89,41 +78,39 @@ class FFMpegRecipe(Recipe): # disable binaries / doc flags += [ - '--disable-ffmpeg', - '--disable-ffplay', - '--disable-ffprobe', - '--disable-ffserver', + '--disable-ffmpeg', + '--disable-ffplay', + '--disable-ffprobe', + '--disable-ffserver', '--disable-doc', ] # other flags: flags += [ - '--enable-filter=aresample,resample,crop,adelay,volume', + '--enable-filter=aresample,resample,crop,adelay,volume,scale', '--enable-protocol=file,http', '--enable-small', '--enable-hwaccels', '--enable-gpl', '--enable-pic', - '--disable-static', + '--disable-static', '--enable-shared', ] # android: flags += [ - '--target-os=android', - '--cross-prefix=arm-linux-androideabi-', + '--target-os=android', + '--cross-prefix=arm-linux-androideabi-', '--arch=arm', '--sysroot=' + self.ctx.ndk_platform, '--enable-neon', '--prefix={}'.format(realpath('.')), ] - cflags = [ - '-march=armv7-a', - '-mfpu=vfpv3-d16', - '-mfloat-abi=softfp', - '-fPIC', - '-DANDROID', - ] + cflags + cflags += [ + '-mfpu=vfpv3-d16', + '-mfloat-abi=softfp', + '-fPIC', + ] env['CFLAGS'] += ' ' + ' '.join(cflags) env['LDFLAGS'] += ' ' + ' '.join(ldflags) @@ -135,4 +122,5 @@ class FFMpegRecipe(Recipe): # copy libs: sh.cp('-a', sh.glob('./lib/lib*.so'), self.ctx.get_libs_dir(arch.arch)) + recipe = FFMpegRecipe() diff --git a/p4a/pythonforandroid/recipes/ffmpeg/patches/configure.patch b/p4a/pythonforandroid/recipes/ffmpeg/patches/configure.patch new file mode 100644 index 00000000..b898c7f5 --- /dev/null +++ b/p4a/pythonforandroid/recipes/ffmpeg/patches/configure.patch @@ -0,0 +1,40 @@ +--- ./configure.orig 2017-12-11 00:35:18.000000000 +0300 ++++ ./configure 2017-12-19 09:47:54.104914600 +0300 +@@ -4841,9 +4841,6 @@ + add_cflags -std=c11 || + check_cflags -std=c99 + +-check_cppflags -D_FILE_OFFSET_BITS=64 +-check_cppflags -D_LARGEFILE_SOURCE +- + add_host_cppflags -D_ISOC99_SOURCE + check_host_cflags -std=c99 + check_host_cflags -Wall +@@ -5979,7 +5976,7 @@ + enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo + enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket + enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new +-enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer ++enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine + enabled libsmbclient && { use_pkg_config libsmbclient smbclient libsmbclient.h smbc_init || + require smbclient libsmbclient.h smbc_init -lsmbclient; } + enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy + +diff -Naur ffmpeg/configure ffmpeg-1/configure +--- ffmpeg/configure 2019-01-11 09:30:02.824961600 +0100 ++++ ffmpeg-1/configure 2019-01-11 09:29:54.976149600 +0100 +@@ -6068,11 +6068,11 @@ + { ! enabled cross_compile && add_cflags -isystem/opt/vc/include/IL && check_header OMX_Core.h ; } || + die "ERROR: OpenMAX IL headers not found"; } + enabled omx && require_header OMX_Core.h +-enabled openssl && { use_pkg_config openssl openssl openssl/ssl.h OPENSSL_init_ssl || ++enabled openssl && { use_pkg_config openssl openssl openssl/ssl.h OPENSSL_init_ssl || + use_pkg_config openssl openssl openssl/ssl.h SSL_library_init || +- check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto || +- check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl32 -leay32 || +- check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto -lws2_32 -lgdi32 || ++ check_lib openssl openssl/ssl.h SSL_library_init -lssl -lcrypto || ++ check_lib openssl openssl/ssl.h SSL_library_init -lssl32 -leay32 || ++ check_lib openssl openssl/ssl.h SSL_library_init -lssl -lcrypto -lws2_32 -lgdi32 || + die "ERROR: openssl not found"; } + enabled rkmpp && { { require_pkg_config rockchip_mpp rockchip_mpp rockchip/rk_mpi.h mpp_create || diff --git a/p4a/pythonforandroid/recipes/ffmpeg/patches/fix-libshine-configure.patch b/p4a/pythonforandroid/recipes/ffmpeg/patches/fix-libshine-configure.patch deleted file mode 100644 index 4be8a231..00000000 --- a/p4a/pythonforandroid/recipes/ffmpeg/patches/fix-libshine-configure.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- ./configure.orig 2016-09-19 04:41:33.000000000 +0300 -+++ ./configure 2016-12-06 19:12:05.046025000 +0300 -@@ -5260,7 +5260,7 @@ - enabled libquvi && require_pkg_config libquvi quvi/quvi.h quvi_init - enabled librtmp && require_pkg_config librtmp librtmp/rtmp.h RTMP_Socket - enabled libschroedinger && require_pkg_config schroedinger-1.0 schroedinger/schro.h schro_init --enabled libshine && require_pkg_config shine shine/layer3.h shine_encode_buffer -+enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine - enabled libsmbclient && { use_pkg_config smbclient libsmbclient.h smbc_init || - require smbclient libsmbclient.h smbc_init -lsmbclient; } - enabled libsnappy && require snappy snappy-c.h snappy_compress -lsnappy diff --git a/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py b/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py index 3ebfed6f..9ff29b72 100644 --- a/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py +++ b/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py @@ -1,27 +1,28 @@ -from pythonforandroid.toolchain import Recipe, CythonRecipe, shprint, current_directory, ArchARM -from os.path import exists, join, realpath -from os import uname -import glob -import sh -import os +from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.toolchain import Recipe +from os.path import join class FFPyPlayerRecipe(CythonRecipe): - version = 'master' + version = '6f7568b498715c2da88f061ebad082a042514923' url = 'https://github.com/matham/ffpyplayer/archive/{version}.zip' - depends = ['python2', 'sdl2', 'ffmpeg'] + depends = [('python2', 'python3'), 'sdl2', 'ffmpeg'] opt_depends = ['openssl', 'ffpyplayer_codecs'] def get_recipe_env(self, arch, with_flags_in_cc=True): env = super(FFPyPlayerRecipe, self).get_recipe_env(arch) - env["SDL_INCLUDE_DIR"] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') - env["SDL_LIB_DIR"] = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch) - build_dir = Recipe.get_recipe('ffmpeg', self.ctx).get_build_dir(arch.arch) env["FFMPEG_INCLUDE_DIR"] = join(build_dir, "include") env["FFMPEG_LIB_DIR"] = join(build_dir, "lib") + env["SDL_INCLUDE_DIR"] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') + env["SDL_LIB_DIR"] = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch) + + env["USE_SDL2_MIXER"] = '1' + env["SDL2_MIXER_INCLUDE_DIR"] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer') + return env + recipe = FFPyPlayerRecipe() diff --git a/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py b/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py index ee70a43f..b3241947 100644 --- a/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py +++ b/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py @@ -7,4 +7,5 @@ class FFPyPlayerCodecsRecipe(Recipe): def build_arch(self, arch): pass + recipe = FFPyPlayerCodecsRecipe() diff --git a/p4a/pythonforandroid/recipes/flask/__init__.py b/p4a/pythonforandroid/recipes/flask/__init__.py index 0a3e6fe0..1a9b6852 100644 --- a/p4a/pythonforandroid/recipes/flask/__init__.py +++ b/p4a/pythonforandroid/recipes/flask/__init__.py @@ -1,15 +1,15 @@ -from pythonforandroid.toolchain import PythonRecipe, shprint -import sh +from pythonforandroid.recipe import PythonRecipe class FlaskRecipe(PythonRecipe): - version = '0.10.1' # The webserver of 'master' seems to fail - # after a little while on Android, so use - # 0.10.1 at least for now + # The webserver of 'master' seems to fail + # after a little while on Android, so use + # 0.10.1 at least for now + version = '0.10.1' url = 'https://github.com/pallets/flask/archive/{version}.zip' - depends = [('python2', 'python3crystax'), 'setuptools'] + depends = [('python2', 'python3', 'python3crystax'), 'setuptools'] python_depends = ['jinja2', 'werkzeug', 'markupsafe', 'itsdangerous', 'click'] diff --git a/p4a/pythonforandroid/recipes/fontconfig/__init__.py b/p4a/pythonforandroid/recipes/fontconfig/__init__.py index f91232e7..8ac01e4e 100644 --- a/p4a/pythonforandroid/recipes/fontconfig/__init__.py +++ b/p4a/pythonforandroid/recipes/fontconfig/__init__.py @@ -1,11 +1,8 @@ - -from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, current_directory, info_main -from os.path import exists, join +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.toolchain import current_directory, shprint import sh - - class FontconfigRecipe(BootstrapNDKRecipe): version = "really_old" url = 'https://github.com/vault/fontconfig/archive/androidbuild.zip' diff --git a/p4a/pythonforandroid/recipes/freetype/__init__.py b/p4a/pythonforandroid/recipes/freetype/__init__.py index 7217dff9..36171ff3 100644 --- a/p4a/pythonforandroid/recipes/freetype/__init__.py +++ b/p4a/pythonforandroid/recipes/freetype/__init__.py @@ -1,19 +1,20 @@ - -from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM +from pythonforandroid.toolchain import Recipe +from pythonforandroid.util import current_directory +from pythonforandroid.logger import shprint from os.path import exists, join, realpath -from os import uname -import glob import sh + class FreetypeRecipe(Recipe): version = '2.5.5' - url = 'http://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' + url = 'http://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' # noqa depends = ['harfbuzz'] def should_build(self, arch): - if exists(join(self.get_build_dir(arch.arch), 'objs', '.libs', 'libfreetype.so')): + if exists(join(self.get_build_dir(arch.arch), + 'objs', '.libs', 'libfreetype.a')): return False return True @@ -23,17 +24,21 @@ class FreetypeRecipe(Recipe): harfbuzz_recipe = Recipe.get_recipe('harfbuzz', self.ctx) env['LDFLAGS'] = ' '.join( [env['LDFLAGS'], - '-L{}'.format(join(harfbuzz_recipe.get_build_dir(arch.arch), 'src', '.libs'))]) + '-L{}'.format(join(harfbuzz_recipe.get_build_dir(arch.arch), + 'src', '.libs'))]) with current_directory(self.get_build_dir(arch.arch)): configure = sh.Command('./configure') - shprint(configure, '--host=arm-linux-androideabi', + shprint(configure, + '--host=arm-linux-androideabi', '--prefix={}'.format(realpath('.')), - '--without-zlib', '--with-png=no', '--enable-shared', + '--without-zlib', + '--with-png=no', + '--disable-shared', _env=env) shprint(sh.make, '-j5', _env=env) - shprint(sh.cp, 'objs/.libs/libfreetype.so', self.ctx.libs_dir) + shprint(sh.cp, 'objs/.libs/libfreetype.a', self.ctx.libs_dir) recipe = FreetypeRecipe() diff --git a/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py b/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py index 7079399a..2d1cdb07 100644 --- a/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py +++ b/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py @@ -1,5 +1,5 @@ -from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, current_directory, info -from os.path import exists, join +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.toolchain import current_directory, shprint import sh @@ -7,18 +7,16 @@ class GenericNDKBuildRecipe(BootstrapNDKRecipe): version = None url = None - depends = [('python2', 'python3crystax')] + depends = [('python2', 'python3', 'python3crystax')] conflicts = ['sdl2', 'pygame', 'sdl'] def should_build(self, arch): return True - def get_recipe_env(self, arch=None): - env = super(GenericNDKBuildRecipe, self).get_recipe_env(arch) - py2 = self.get_recipe('python2', arch.ctx) - env['PYTHON2_NAME'] = py2.get_dir_name() - if 'python2' in self.ctx.recipe_build_order: - env['EXTRA_LDLIBS'] = ' -lpython2.7' + def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True): + env = super(GenericNDKBuildRecipe, self).get_recipe_env( + arch=arch, with_flags_in_cc=with_flags_in_cc, with_python=with_python) + env['APP_ALLOW_MISSING_DEPS'] = 'true' return env def build_arch(self, arch): diff --git a/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py b/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py index a0bab422..598ca130 100644 --- a/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py +++ b/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py @@ -1,11 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class GeventWebsocketRecipe(PythonRecipe): version = '0.9.5' url = 'https://pypi.python.org/packages/source/g/gevent-websocket/gevent-websocket-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools'] + depends = ['setuptools'] site_packages_name = 'geventwebsocket' call_hostpython_via_targetpython = False + recipe = GeventWebsocketRecipe() diff --git a/p4a/pythonforandroid/recipes/gevent/__init__.py b/p4a/pythonforandroid/recipes/gevent/__init__.py index c3a9957a..5933fb33 100644 --- a/p4a/pythonforandroid/recipes/gevent/__init__.py +++ b/p4a/pythonforandroid/recipes/gevent/__init__.py @@ -1,10 +1,32 @@ -from pythonforandroid.toolchain import CompiledComponentsPythonRecipe +import re +from pythonforandroid.logger import info +from pythonforandroid.recipe import CythonRecipe -class GeventRecipe(CompiledComponentsPythonRecipe): - version = '1.1.1' +class GeventRecipe(CythonRecipe): + version = '1.4.0' url = 'https://pypi.python.org/packages/source/g/gevent/gevent-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'greenlet'] - patches = ["gevent.patch"] + depends = ['librt', 'greenlet'] + patches = ["cross_compiling.patch"] + + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + """ + - Moves all -I -D from CFLAGS to CPPFLAGS environment. + - Moves all -l from LDFLAGS to LIBS environment. + - Fixes linker name (use cross compiler) and flags (appends LIBS) + """ + env = super(GeventRecipe, self).get_recipe_env(arch, with_flags_in_cc) + # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS + regex = re.compile(r'(?:\s|^)-[DI][\S]+') + env['CPPFLAGS'] = ''.join(re.findall(regex, env['CFLAGS'])).strip() + env['CFLAGS'] = re.sub(regex, '', env['CFLAGS']) + info('Moved "{}" from CFLAGS to CPPFLAGS.'.format(env['CPPFLAGS'])) + # LDFLAGS may only be used to specify linker flags, for libraries use LIBS + regex = re.compile(r'(?:\s|^)-l[\w\.]+') + env['LIBS'] = ''.join(re.findall(regex, env['LDFLAGS'])).strip() + env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS']) + info('Moved "{}" from LDFLAGS to LIBS.'.format(env['LIBS'])) + return env + recipe = GeventRecipe() diff --git a/p4a/pythonforandroid/recipes/gevent/cross_compiling.patch b/p4a/pythonforandroid/recipes/gevent/cross_compiling.patch new file mode 100644 index 00000000..01e55d8c --- /dev/null +++ b/p4a/pythonforandroid/recipes/gevent/cross_compiling.patch @@ -0,0 +1,26 @@ +diff --git a/_setupares.py b/_setupares.py +index dd184de6..bb16bebe 100644 +--- a/_setupares.py ++++ b/_setupares.py +@@ -43,7 +43,7 @@ else: + ares_configure_command = ' '.join([ + "(cd ", quoted_dep_abspath('c-ares'), + " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ", +- " && sh ./configure --disable-dependency-tracking " + _m32 + "CONFIG_COMMANDS= ", ++ " && sh ./configure --host={} --disable-dependency-tracking ".format(os.environ['TOOLCHAIN_PREFIX']) + _m32 + "CONFIG_COMMANDS= ", + " && cp ares_config.h ares_build.h \"$OLDPWD\" ", + " && cat ares_build.h ", + " && if [ -r ares_build.h.orig ]; then mv ares_build.h.orig ares_build.h; fi)", +diff --git a/_setuplibev.py b/_setuplibev.py +index 2a5841bf..b6433c94 100644 +--- a/_setuplibev.py ++++ b/_setuplibev.py +@@ -31,7 +31,7 @@ LIBEV_EMBED = should_embed('libev') + # and the PyPy branch will clean it up. + libev_configure_command = ' '.join([ + "(cd ", quoted_dep_abspath('libev'), +- " && sh ./configure ", ++ " && sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']), + " && cp config.h \"$OLDPWD\"", + ")", + '> configure-output.txt' diff --git a/p4a/pythonforandroid/recipes/gevent/gevent.patch b/p4a/pythonforandroid/recipes/gevent/gevent.patch deleted file mode 100644 index 4b4b673f..00000000 --- a/p4a/pythonforandroid/recipes/gevent/gevent.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff -Naur gevent-1.1.1/setup.py gevent-1.1.1_diff/setup.py ---- gevent-1.1.1/setup.py 2016-04-04 17:27:33.000000000 +0200 -+++ gevent-1.1.1_diff/setup.py 2016-05-10 10:10:39.145881610 +0200 -@@ -96,7 +96,7 @@ - # and the PyPy branch will clean it up. - libev_configure_command = ' '.join([ - "(cd ", _quoted_abspath('libev/'), -- " && /bin/sh ./configure ", -+ " && /bin/sh ./configure --host={}".format(os.environ['TOOLCHAIN_PREFIX']), - " && cp config.h \"$OLDPWD\"", - ")", - '> configure-output.txt' -@@ -112,7 +112,7 @@ - # Use -r, not -e, for support of old solaris. See https://github.com/gevent/gevent/issues/777 - ares_configure_command = ' '.join(["(cd ", _quoted_abspath('c-ares/'), - " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ", -- " && /bin/sh ./configure " + _m32 + "CONFIG_COMMANDS= CONFIG_FILES= ", -+ " && /bin/sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']) + "CFLAGS= LDFLAGS= CONFIG_COMMANDS= CONFIG_FILES= ", - " && cp ares_config.h ares_build.h \"$OLDPWD\" ", - " && mv ares_build.h.orig ares_build.h)", - "> configure-output.txt"]) diff --git a/p4a/pythonforandroid/recipes/greenlet/__init__.py b/p4a/pythonforandroid/recipes/greenlet/__init__.py index a1275814..3f2043d5 100644 --- a/p4a/pythonforandroid/recipes/greenlet/__init__.py +++ b/p4a/pythonforandroid/recipes/greenlet/__init__.py @@ -1,9 +1,11 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import CompiledComponentsPythonRecipe -class GreenletRecipe(PythonRecipe): - version = '0.4.9' +class GreenletRecipe(CompiledComponentsPythonRecipe): + version = '0.4.15' url = 'https://pypi.python.org/packages/source/g/greenlet/greenlet-{version}.tar.gz' - depends = [('python2', 'python3crystax')] + depends = ['setuptools'] + call_hostpython_via_targetpython = False + recipe = GreenletRecipe() diff --git a/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py b/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py new file mode 100644 index 00000000..62344f00 --- /dev/null +++ b/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py @@ -0,0 +1,11 @@ +from pythonforandroid.recipe import CythonRecipe + + +class GroestlcoinHashRecipe(CythonRecipe): + version = '1.0.1' + url = 'https://github.com/Groestlcoin/groestlcoin-hash-python/archive/{version}.tar.gz' + depends = [] + cythonize = False + + +recipe = GroestlcoinHashRecipe() diff --git a/p4a/pythonforandroid/recipes/harfbuzz/__init__.py b/p4a/pythonforandroid/recipes/harfbuzz/__init__.py index 1df851fb..32f4e513 100644 --- a/p4a/pythonforandroid/recipes/harfbuzz/__init__.py +++ b/p4a/pythonforandroid/recipes/harfbuzz/__init__.py @@ -1,17 +1,17 @@ - -from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM -from os.path import exists, join, realpath -from os import uname -import glob +from pythonforandroid.toolchain import Recipe +from pythonforandroid.util import current_directory +from pythonforandroid.logger import shprint +from os.path import exists, join import sh class HarfbuzzRecipe(Recipe): version = '0.9.40' - url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.bz2' + url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.bz2' # noqa def should_build(self, arch): - if exists(join(self.get_build_dir(arch.arch), 'src', '.libs', 'libharfbuzz.so')): + if exists(join(self.get_build_dir(arch.arch), + 'src', '.libs', 'libharfbuzz.a')): return False return True @@ -24,10 +24,16 @@ class HarfbuzzRecipe(Recipe): with current_directory(self.get_build_dir(arch.arch)): configure = sh.Command('./configure') shprint(configure, '--without-icu', '--host=arm-linux=androideabi', - '--prefix={}'.format(join(self.ctx.build_dir, 'python-install')), - '--without-freetype', '--without-glib', _env=env) + '--prefix={}'.format( + join(self.ctx.build_dir, 'python-install')), + '--without-freetype', + '--without-glib', + '--disable-shared', + _env=env) shprint(sh.make, '-j5', _env=env) - shprint(sh.cp, '-L', join('src', '.libs', 'libharfbuzz.so'), self.ctx.libs_dir) + shprint(sh.cp, '-L', join('src', '.libs', 'libharfbuzz.a'), + self.ctx.libs_dir) + recipe = HarfbuzzRecipe() diff --git a/p4a/pythonforandroid/recipes/hostpython2/__init__.py b/p4a/pythonforandroid/recipes/hostpython2/__init__.py index 785c4b73..39a75e43 100644 --- a/p4a/pythonforandroid/recipes/hostpython2/__init__.py +++ b/p4a/pythonforandroid/recipes/hostpython2/__init__.py @@ -1,59 +1,18 @@ - -from pythonforandroid.toolchain import Recipe, shprint, current_directory, info, warning -from os.path import join, exists -from os import chdir -import sh +from pythonforandroid.python import HostPythonRecipe -class Hostpython2Recipe(Recipe): - version = '2.7.2' - url = 'http://python.org/ftp/python/{version}/Python-{version}.tar.bz2' +class Hostpython2Recipe(HostPythonRecipe): + ''' + The hostpython2's recipe. + + .. versionchanged:: 0.6.0 + Updated to version 2.7.15 and the build process has been changed in + favour of the recently added class + :class:`~pythonforandroid.python.HostPythonRecipe` + ''' + version = '2.7.15' name = 'hostpython2' - - conflicts = ['hostpython3'] - - def get_build_container_dir(self, arch=None): - choices = self.check_recipe_choices() - dir_name = '-'.join([self.name] + choices) - return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') - - def get_build_dir(self, arch=None): - return join(self.get_build_container_dir(), self.name) - - def prebuild_arch(self, arch): - # Override hostpython Setup? - shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'), - join(self.get_build_dir(), 'Modules', 'Setup')) - - def build_arch(self, arch): - with current_directory(self.get_build_dir()): - - if exists('hostpython'): - info('hostpython already exists, skipping build') - self.ctx.hostpython = join(self.get_build_dir(), - 'hostpython') - self.ctx.hostpgen = join(self.get_build_dir(), - 'hostpgen') - return - - configure = sh.Command('./configure') - - shprint(configure) - shprint(sh.make, '-j5') - - shprint(sh.mv, join('Parser', 'pgen'), 'hostpgen') - - if exists('python.exe'): - shprint(sh.mv, 'python.exe', 'hostpython') - elif exists('python'): - shprint(sh.mv, 'python', 'hostpython') - else: - warning('Unable to find the python executable after ' - 'hostpython build! Exiting.') - exit(1) - - self.ctx.hostpython = join(self.get_build_dir(), 'hostpython') - self.ctx.hostpgen = join(self.get_build_dir(), 'hostpgen') + conflicts = ['hostpython3', 'hostpython3crystax', 'hostpython2legacy'] recipe = Hostpython2Recipe() diff --git a/p4a/pythonforandroid/recipes/hostpython2/Setup b/p4a/pythonforandroid/recipes/hostpython2legacy/Setup similarity index 97% rename from p4a/pythonforandroid/recipes/hostpython2/Setup rename to p4a/pythonforandroid/recipes/hostpython2legacy/Setup index 58207718..d21c8936 100644 --- a/p4a/pythonforandroid/recipes/hostpython2/Setup +++ b/p4a/pythonforandroid/recipes/hostpython2legacy/Setup @@ -91,7 +91,7 @@ SITEPATH= TESTPATH= # Path components for machine- or system-dependent modules and shared libraries -MACHDEPPATH=:plat-$(PLATDIR) +MACHDEPPATH=:plat-$(MACHDEP) EXTRAMACHDEPPATH= # Path component for the Tkinter-related modules @@ -109,7 +109,7 @@ PYTHONPATH=$(COREPYTHONPATH) # various reasons; therefore they are listed here instead of in the # normal order. -# This only contains the minimal set of modules required to run the +# This only contains the minimal set of modules required to run the # setup.py script in the root of the Python source tree. posix posixmodule.c # posix (UNIX) system calls @@ -118,7 +118,6 @@ pwd pwdmodule.c # this is needed to find out the user's home dir # if $HOME is not set _sre _sre.c # Fredrik Lundh's new regular expressions _codecs _codecsmodule.c # access to the builtin codecs and codec registry -_weakref _weakref.c # weak referencess # The zipimport module is always imported at startup. Having it as a # builtin module avoids some bootstrapping problems and reduces overhead. @@ -127,9 +126,9 @@ zipimport zipimport.c # The rest of the modules listed in this file are all commented out by # default. Usually they can be detected and built as dynamically # loaded modules by the new setup.py script added in Python 2.1. If -# you're on a platform that doesn't support dynamic loading, want to -# compile modules statically into the Python binary, or need to -# specify some odd set of compiler switches, you can uncomment the +# you're on a platform that doesn't support dynamic loading, want to +# compile modules statically into the Python binary, or need to +# specify some odd set of compiler switches, you can uncomment the # appropriate lines below. # ====================================================================== @@ -165,19 +164,20 @@ GLHACK=-Dclear=__GLclear #readline readline.c -lreadline -ltermcap + # Modules that should always be present (non UNIX dependent): array arraymodule.c # array objects -cmath cmathmodule.c _math.c # -lm # complex math library functions -math mathmodule.c _math.c # -lm # math library functions, e.g. sin() +cmath cmathmodule.c # -lm # complex math library functions +math mathmodule.c # -lm # math library functions, e.g. sin() _struct _struct.c # binary structure packing/unpacking time timemodule.c # -lm # time operations and variables operator operator.c # operator.add() and similar goodies +_weakref _weakref.c # basic weak reference support #_testcapi _testcapimodule.c # Python C API test module _random _randommodule.c # Random number generator _collections _collectionsmodule.c # Container types -_heapq _heapqmodule.c # Heapq type -itertools itertoolsmodule.c # Functions creating iterators for efficient looping +itertools itertoolsmodule.c # Functions creating iterators for efficient looping strop stropmodule.c # String manipulations _functools _functoolsmodule.c # Tools for working with functions and callable objects _elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator @@ -185,7 +185,7 @@ _elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI datetime datetimemodule.c # date/time type _bisect _bisectmodule.c # Bisection algorithms -unicodedata unicodedata.c # static Unicode character database +#unicodedata unicodedata.c # static Unicode character database # access to ISO C locale support #_locale _localemodule.c # -lintl @@ -196,7 +196,7 @@ unicodedata unicodedata.c # static Unicode character database # supported...) fcntl fcntlmodule.c # fcntl(2) and ioctl(2) -#spwd spwdmodule.c # spwd(3) +#spwd spwdmodule.c # spwd(3) #grp grpmodule.c # grp(3) select selectmodule.c # select(2); not on ancient System V @@ -299,7 +299,7 @@ _sha512 sha512module.c #sunaudiodev sunaudiodev.c -# A Linux specific module -- off by default; this may also work on +# A Linux specific module -- off by default; this may also work on # some *BSDs. #linuxaudiodev linuxaudiodev.c @@ -365,7 +365,7 @@ _sha512 sha512module.c #_curses _cursesmodule.c -lcurses -ltermcap # Wrapper for the panel library that's part of ncurses and SYSV curses. -#_curses_panel _curses_panel.c -lpanel -lncurses +#_curses_panel _curses_panel.c -lpanel -lncurses # Generic (SunOS / SVR4) dynamic loading module. diff --git a/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py b/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py new file mode 100644 index 00000000..0a025737 --- /dev/null +++ b/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py @@ -0,0 +1,67 @@ +import os +import sh +from os.path import join, exists + +from pythonforandroid.recipe import Recipe +from pythonforandroid.logger import info, warning, shprint +from pythonforandroid.util import current_directory + + +class Hostpython2LegacyRecipe(Recipe): + ''' + .. versionadded:: 0.6.0 + This was the original hostpython2's recipe by tito reintroduced as + hostpython2legacy. + ''' + version = '2.7.2' + url = 'https://python.org/ftp/python/{version}/Python-{version}.tar.bz2' + name = 'hostpython2legacy' + patches = ['fix-segfault-pygchead.patch'] + + conflicts = ['hostpython2', 'hostpython3', 'hostpython3crystax'] + + def get_build_container_dir(self, arch=None): + choices = self.check_recipe_choices() + dir_name = '-'.join([self.name] + choices) + return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') + + def get_build_dir(self, arch=None): + return join(self.get_build_container_dir(), self.name) + + def prebuild_arch(self, arch): + # Override hostpython Setup? + shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'), + join(self.get_build_dir(), 'Modules', 'Setup')) + + def build_arch(self, arch): + with current_directory(self.get_build_dir()): + + if exists('hostpython'): + info('hostpython already exists, skipping build') + self.ctx.hostpython = join(self.get_build_dir(), 'hostpython') + self.ctx.hostpgen = join(self.get_build_dir(), 'hostpgen') + return + + if 'LIBS' in os.environ: + os.environ.pop('LIBS') + configure = sh.Command('./configure') + + shprint(configure) + shprint(sh.make, '-j5') + + shprint(sh.mv, join('Parser', 'pgen'), 'hostpgen') + + if exists('python.exe'): + shprint(sh.mv, 'python.exe', 'hostpython') + elif exists('python'): + shprint(sh.mv, 'python', 'hostpython') + else: + warning('Unable to find the python executable after ' + 'hostpython build! Exiting.') + exit(1) + + self.ctx.hostpython = join(self.get_build_dir(), 'hostpython') + self.ctx.hostpgen = join(self.get_build_dir(), 'hostpgen') + + +recipe = Hostpython2LegacyRecipe() diff --git a/p4a/pythonforandroid/recipes/hostpython2legacy/fix-segfault-pygchead.patch b/p4a/pythonforandroid/recipes/hostpython2legacy/fix-segfault-pygchead.patch new file mode 100644 index 00000000..25d4599c --- /dev/null +++ b/p4a/pythonforandroid/recipes/hostpython2legacy/fix-segfault-pygchead.patch @@ -0,0 +1,12 @@ +diff -Naur Python-2.7.2.orig/Include/objimpl.h Python-2.7.2/Include/objimpl.h +--- Python-2.7.2.orig/Include/objimpl.h 2011-06-11 17:46:23.000000000 +0200 ++++ Python-2.7.2/Include/objimpl.h 2018-09-04 17:33:09.254654565 +0200 +@@ -255,7 +255,7 @@ + union _gc_head *gc_prev; + Py_ssize_t gc_refs; + } gc; +- long double dummy; /* force worst-case alignment */ ++ double dummy; /* force worst-case alignment */ + } PyGC_Head; + + extern PyGC_Head *_PyGC_generation0; diff --git a/p4a/pythonforandroid/recipes/hostpython3/__init__.py b/p4a/pythonforandroid/recipes/hostpython3/__init__.py index 666c0830..8b268bdd 100644 --- a/p4a/pythonforandroid/recipes/hostpython3/__init__.py +++ b/p4a/pythonforandroid/recipes/hostpython3/__init__.py @@ -1,61 +1,17 @@ - -from pythonforandroid.toolchain import Recipe, shprint, current_directory, info, warning -from os.path import join, exists -from os import chdir -import sh +from pythonforandroid.python import HostPythonRecipe -class Hostpython3Recipe(Recipe): - version = '3.5' - # url = 'http://python.org/ftp/python/{version}/Python-{version}.tgz' - url = 'https://github.com/crystax/android-vendor-python-3-5/archive/master.zip' +class Hostpython3Recipe(HostPythonRecipe): + ''' + The hostpython3's recipe. + + .. versionchanged:: 0.6.0 + Refactored into the new class + :class:`~pythonforandroid.python.HostPythonRecipe` + ''' + version = '3.7.1' name = 'hostpython3' - - conflicts = ['hostpython2'] - - # def prebuild_armeabi(self): - # # Override hostpython Setup? - # shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'), - # join(self.get_build_dir('armeabi'), 'Modules', 'Setup')) - - def build_arch(self, arch): - # AND: Should use an i386 recipe system - warning('Running hostpython build. Arch is armeabi! ' - 'This is naughty, need to fix the Arch system!') - - # AND: Fix armeabi again - with current_directory(self.get_build_dir(arch.arch)): - - if exists('hostpython'): - info('hostpython already exists, skipping build') - self.ctx.hostpython = join(self.get_build_dir('armeabi'), - 'hostpython') - self.ctx.hostpgen = join(self.get_build_dir('armeabi'), - 'hostpgen') - return - - configure = sh.Command('./configure') - - shprint(configure) - shprint(sh.make, '-j5', 'BUILDPYTHON=hostpython', 'hostpython', - 'PGEN=Parser/hostpgen', 'Parser/hostpgen') - - shprint(sh.mv, join('Parser', 'hostpgen'), 'hostpgen') - - # if exists('python.exe'): - # shprint(sh.mv, 'python.exe', 'hostpython') - # elif exists('python'): - # shprint(sh.mv, 'python', 'hostpython') - if exists('hostpython'): - pass # The above commands should automatically create - # the hostpython binary, unlike with python2 - else: - warning('Unable to find the python executable after ' - 'hostpython build! Exiting.') - exit(1) - - self.ctx.hostpython = join(self.get_build_dir(arch.arch), 'hostpython') - self.ctx.hostpgen = join(self.get_build_dir(arch.arch), 'hostpgen') + conflicts = ['hostpython2', 'hostpython3crystax'] recipe = Hostpython3Recipe() diff --git a/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py b/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py index a629a884..88cee359 100644 --- a/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py +++ b/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py @@ -1,26 +1,44 @@ - -from pythonforandroid.toolchain import Recipe, shprint, current_directory, info, warning -from os.path import join, exists -from os import chdir +from pythonforandroid.toolchain import Recipe, shprint +from os.path import join import sh -class Hostpython3Recipe(Recipe): - version = '3.5' - # url = 'http://python.org/ftp/python/{version}/Python-{version}.tgz' - # url = 'https://github.com/crystax/android-vendor-python-3-5/archive/master.zip' +class Hostpython3CrystaXRecipe(Recipe): + version = 'auto' # the version is taken from the python3crystax recipe name = 'hostpython3crystax' conflicts = ['hostpython2'] + def get_build_container_dir(self, arch=None): + choices = self.check_recipe_choices() + dir_name = '-'.join([self.name] + choices) + return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') + # def prebuild_armeabi(self): # # Override hostpython Setup? # shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'), # join(self.get_build_dir('armeabi'), 'Modules', 'Setup')) + def get_build_dir(self, arch=None): + return join(self.get_build_container_dir(), self.name) + def build_arch(self, arch): + """ + Creates expected build and symlinks system Python version. + """ self.ctx.hostpython = '/usr/bin/false' - self.ctx.hostpgen = '/usr/bin/false' + # creates the sub buildir (used by other recipes) + # https://github.com/kivy/python-for-android/issues/1154 + sub_build_dir = join(self.get_build_dir(), 'build') + shprint(sh.mkdir, '-p', sub_build_dir) + python3crystax = self.get_recipe('python3crystax', self.ctx) + system_python = sh.which("python" + python3crystax.version) + if system_python is None: + raise OSError( + ('Trying to use python3crystax=={} but this Python version ' + 'is not installed locally.').format(python3crystax.version)) + link_dest = join(self.get_build_dir(), 'hostpython') + shprint(sh.ln, '-sf', system_python, link_dest) -recipe = Hostpython3Recipe() +recipe = Hostpython3CrystaXRecipe() diff --git a/p4a/pythonforandroid/recipes/icu/__init__.py b/p4a/pythonforandroid/recipes/icu/__init__.py index a3a5bfea..4bb2de0c 100644 --- a/p4a/pythonforandroid/recipes/icu/__init__.py +++ b/p4a/pythonforandroid/recipes/icu/__init__.py @@ -2,7 +2,7 @@ import sh import os from os.path import join, isdir from pythonforandroid.recipe import NDKRecipe -from pythonforandroid.toolchain import shprint, info +from pythonforandroid.toolchain import shprint from pythonforandroid.util import current_directory, ensure_dir @@ -11,7 +11,7 @@ class ICURecipe(NDKRecipe): version = '57.1' url = 'http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-src.tgz' - depends = [('python2', 'python3crystax'), 'hostpython2'] # installs in python + depends = [('hostpython2', 'hostpython3')] # installs in python generated_libraries = [ 'libicui18n.so', 'libicuuc.so', 'libicudata.so', 'libicule.so'] diff --git a/p4a/pythonforandroid/recipes/idna/__init__.py b/p4a/pythonforandroid/recipes/idna/__init__.py index 7d534f4f..22fa41f9 100644 --- a/p4a/pythonforandroid/recipes/idna/__init__.py +++ b/p4a/pythonforandroid/recipes/idna/__init__.py @@ -2,13 +2,13 @@ from pythonforandroid.recipe import PythonRecipe class IdnaRecipe(PythonRecipe): - name = 'idna' - version = '2.0' - url = 'https://pypi.python.org/packages/source/i/idna/idna-{version}.tar.gz' + name = 'idna' + version = '2.8' + url = 'https://github.com/kjd/idna/archive/v{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools'] + depends = ['setuptools'] - call_hostpython_via_targetpython = False + call_hostpython_via_targetpython = False recipe = IdnaRecipe() diff --git a/p4a/pythonforandroid/recipes/ifaddrs/__init__.py b/p4a/pythonforandroid/recipes/ifaddrs/__init__.py new file mode 100644 index 00000000..47c0008f --- /dev/null +++ b/p4a/pythonforandroid/recipes/ifaddrs/__init__.py @@ -0,0 +1,54 @@ +""" ifaddrs for Android +""" +from os.path import join, exists +import sh +from pythonforandroid.logger import info, shprint +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from pythonforandroid.toolchain import current_directory + + +class IFAddrRecipe(CompiledComponentsPythonRecipe): + version = '8f9a87c' + url = 'https://github.com/morristech/android-ifaddrs/archive/{version}.zip' + depends = [('hostpython2', 'hostpython3')] + + call_hostpython_via_targetpython = False + site_packages_name = 'ifaddrs' + generated_libraries = ['libifaddrs.so'] + + def prebuild_arch(self, arch): + """Make the build and target directories""" + path = self.get_build_dir(arch.arch) + if not exists(path): + info("creating {}".format(path)) + shprint(sh.mkdir, '-p', path) + + def build_arch(self, arch): + """simple shared compile""" + env = self.get_recipe_env(arch, with_flags_in_cc=False) + for path in ( + self.get_build_dir(arch.arch), + join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'), + join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')): + if not exists(path): + info("creating {}".format(path)) + shprint(sh.mkdir, '-p', path) + cli = env['CC'].split()[0] + # makes sure first CC command is the compiler rather than ccache, refs: + # https://github.com/kivy/python-for-android/issues/1398 + if 'ccache' in cli: + cli = env['CC'].split()[1] + cc = sh.Command(cli) + + with current_directory(self.get_build_dir(arch.arch)): + cflags = env['CFLAGS'].split() + cflags.extend(['-I.', '-c', '-l.', 'ifaddrs.c', '-I.']) + shprint(cc, *cflags, _env=env) + cflags = env['CFLAGS'].split() + cflags.extend(['-shared', '-I.', 'ifaddrs.o', '-o', 'libifaddrs.so']) + cflags.extend(env['LDFLAGS'].split()) + shprint(cc, *cflags, _env=env) + shprint(sh.cp, 'libifaddrs.so', self.ctx.get_libs_dir(arch.arch)) + + +recipe = IFAddrRecipe() diff --git a/p4a/pythonforandroid/recipes/ipaddress/__init__.py b/p4a/pythonforandroid/recipes/ipaddress/__init__.py index 16a468cd..edc9f422 100644 --- a/p4a/pythonforandroid/recipes/ipaddress/__init__.py +++ b/p4a/pythonforandroid/recipes/ipaddress/__init__.py @@ -2,11 +2,10 @@ from pythonforandroid.recipe import PythonRecipe class IpaddressRecipe(PythonRecipe): - name = 'ipaddress' - version = '1.0.16' - url = 'https://pypi.python.org/packages/source/i/ipaddress/ipaddress-{version}.tar.gz' - - depends = ['python2'] + name = 'ipaddress' + version = '1.0.22' + url = 'https://github.com/phihag/ipaddress/archive/v{version}.tar.gz' + depends = ['setuptools'] recipe = IpaddressRecipe() diff --git a/p4a/pythonforandroid/recipes/jedi/__init__.py b/p4a/pythonforandroid/recipes/jedi/__init__.py index ea076837..6338a52f 100644 --- a/p4a/pythonforandroid/recipes/jedi/__init__.py +++ b/p4a/pythonforandroid/recipes/jedi/__init__.py @@ -1,13 +1,11 @@ - -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class JediRecipe(PythonRecipe): - # version = 'master' version = 'v0.9.0' url = 'https://github.com/davidhalter/jedi/archive/{version}.tar.gz' - depends = [('python2', 'python3crystax')] + depends = [('python2', 'python3crystax', 'python3')] patches = ['fix_MergedNamesDict_get.patch'] # This apparently should be fixed in jedi 0.10 (not released to diff --git a/p4a/pythonforandroid/recipes/jpeg/Application.mk b/p4a/pythonforandroid/recipes/jpeg/Application.mk index 608b6db8..5942a034 100644 --- a/p4a/pythonforandroid/recipes/jpeg/Application.mk +++ b/p4a/pythonforandroid/recipes/jpeg/Application.mk @@ -1,3 +1,4 @@ APP_OPTIM := release APP_ABI := all # or armeabi APP_MODULES := libjpeg +APP_ALLOW_MISSING_DEPS := true diff --git a/p4a/pythonforandroid/recipes/jpeg/__init__.py b/p4a/pythonforandroid/recipes/jpeg/__init__.py index 052a735f..1969d2c1 100644 --- a/p4a/pythonforandroid/recipes/jpeg/__init__.py +++ b/p4a/pythonforandroid/recipes/jpeg/__init__.py @@ -1,35 +1,78 @@ -from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.recipe import Recipe from pythonforandroid.logger import shprint from pythonforandroid.util import current_directory from os.path import join, exists +from os import environ, uname +from glob import glob import sh -class JpegRecipe(NDKRecipe): - name = 'jpeg' - version = 'linaro-android' - url = 'git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git' +class JpegRecipe(Recipe): + ''' + .. versionchanged:: 0.6.0 + rewrote recipe to be build with clang and updated libraries to latest + version of the official git repo. + ''' + name = 'jpeg' + version = '2.0.1' + url = 'https://github.com/libjpeg-turbo/libjpeg-turbo/archive/{version}.tar.gz' # noqa + # we will require this below patch to build the shared library + # patches = ['remove-version.patch'] - patches = ['build-static.patch'] + def should_build(self, arch): + return not exists(join(self.get_build_dir(arch.arch), + 'libturbojpeg.a')) - generated_libraries = ['libjpeg.a'] + def build_arch(self, arch): + super(JpegRecipe, self).build_arch(arch) + build_dir = self.get_build_dir(arch.arch) - def prebuild_arch(self, arch): - super(JpegRecipe, self).prebuild_arch(arch) + # TODO: Fix simd/neon + with current_directory(build_dir): + env = self.get_recipe_env(arch) + toolchain_file = join(self.ctx.ndk_dir, + 'build/cmake/android.toolchain.cmake') - build_dir = self.get_build_dir(arch.arch) - app_mk = join(build_dir, 'Application.mk') - if not exists(app_mk): - shprint(sh.cp, join(self.get_recipe_dir(), 'Application.mk'), app_mk) - jni_ln = join(build_dir, 'jni') - if not exists(jni_ln): - shprint(sh.ln, '-s', build_dir, jni_ln) + shprint(sh.rm, '-f', 'CMakeCache.txt', 'CMakeFiles/') + shprint(sh.cmake, '-G', 'Unix Makefiles', + '-DCMAKE_SYSTEM_NAME=Android', + '-DCMAKE_SYSTEM_PROCESSOR={cpu}'.format(cpu='arm'), + '-DCMAKE_POSITION_INDEPENDENT_CODE=1', + '-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch), + '-DCMAKE_ANDROID_NDK=' + self.ctx.ndk_dir, + '-DCMAKE_C_COMPILER={toolchain}/bin/clang'.format( + toolchain=env['TOOLCHAIN']), + '-DCMAKE_CXX_COMPILER={toolchain}/bin/clang++'.format( + toolchain=env['TOOLCHAIN']), + '-DCMAKE_BUILD_TYPE=Release', + '-DCMAKE_INSTALL_PREFIX=./install', + '-DCMAKE_TOOLCHAIN_FILE=' + toolchain_file, - def build_arch(self, arch): - super(JpegRecipe, self).build_arch(arch) - with current_directory(self.get_lib_dir(arch)): - shprint(sh.mv, 'libjpeg.a', 'libjpeg-orig.a') - shprint(sh.ar, '-rcT', 'libjpeg.a', 'libjpeg-orig.a', 'libsimd.a') + '-DANDROID_ABI={arch}'.format(arch=arch.arch), + '-DANDROID_ARM_NEON=ON', + '-DENABLE_NEON=ON', + # '-DREQUIRE_SIMD=1', + + # Force disable shared, with the static ones is enough + '-DENABLE_SHARED=0', + '-DENABLE_STATIC=1', + _env=env) + shprint(sh.make, _env=env) + + # copy static libs to libs collection + for lib in glob(join(build_dir, '*.a')): + shprint(sh.cp, '-L', lib, self.ctx.libs_dir) + + def get_recipe_env(self, arch=None, with_flags_in_cc=False, clang=True): + env = environ.copy() + + build_platform = '{system}-{machine}'.format( + system=uname()[0], machine=uname()[-1]).lower() + env['TOOLCHAIN'] = join(self.ctx.ndk_dir, 'toolchains/llvm/' + 'prebuilt/{build_platform}'.format( + build_platform=build_platform)) + + return env recipe = JpegRecipe() diff --git a/p4a/pythonforandroid/recipes/jpeg/remove-version.patch b/p4a/pythonforandroid/recipes/jpeg/remove-version.patch new file mode 100644 index 00000000..311aa33b --- /dev/null +++ b/p4a/pythonforandroid/recipes/jpeg/remove-version.patch @@ -0,0 +1,12 @@ +--- jpeg/CMakeLists.txt.orig 2018-11-12 20:20:28.000000000 +0100 ++++ jpeg/CMakeLists.txt 2018-12-14 12:43:45.338704504 +0100 +@@ -573,6 +573,9 @@ + add_library(turbojpeg SHARED ${TURBOJPEG_SOURCES}) + set_property(TARGET turbojpeg PROPERTY COMPILE_FLAGS + "-DBMP_SUPPORTED -DPPM_SUPPORTED") ++ set_property(TARGET jpeg PROPERTY NO_SONAME 1) ++ set_property(TARGET turbojpeg PROPERTY NO_SONAME 1) ++ set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "") + if(WIN32) + set_target_properties(turbojpeg PROPERTIES DEFINE_SYMBOL DLLDEFINE) + endif() diff --git a/p4a/pythonforandroid/recipes/kivent_core/__init__.py b/p4a/pythonforandroid/recipes/kivent_core/__init__.py deleted file mode 100644 index 044aca0e..00000000 --- a/p4a/pythonforandroid/recipes/kivent_core/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -from pythonforandroid.toolchain import CythonRecipe -from os.path import join - - -class KiventCoreRecipe(CythonRecipe): - version = 'master' - url = 'https://github.com/kivy/kivent/archive/{version}.zip' - name = 'kivent_core' - - depends = ['kivy'] - - subbuilddir = False - - def get_recipe_env(self, arch, with_flags_in_cc=True): - env = super(KiventCoreRecipe, self).get_recipe_env( - arch, with_flags_in_cc=with_flags_in_cc) - env['CYTHONPATH'] = self.get_recipe( - 'kivy', self.ctx).get_build_dir(arch.arch) - return env - - def get_build_dir(self, arch, sub=False): - builddir = super(KiventCoreRecipe, self).get_build_dir(arch) - if sub or self.subbuilddir: - return join(builddir, 'modules', 'core') - else: - return builddir - - def build_arch(self, arch): - self.subbuilddir = True - super(KiventCoreRecipe, self).build_arch(arch) - self.subbuilddir = False - - -recipe = KiventCoreRecipe() diff --git a/p4a/pythonforandroid/recipes/kivent_cymunk/__init__.py b/p4a/pythonforandroid/recipes/kivent_cymunk/__init__.py deleted file mode 100644 index 38132546..00000000 --- a/p4a/pythonforandroid/recipes/kivent_cymunk/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -from pythonforandroid.toolchain import CythonRecipe -from os.path import join - - -class KiventCymunkRecipe(CythonRecipe): - name = 'kivent_cymunk' - - depends = ['kivent_core', 'cymunk'] - - subbuilddir = False - - def get_recipe_env(self, arch, with_flags_in_cc=True): - env = super(KiventCymunkRecipe, self).get_recipe_env( - arch, with_flags_in_cc=with_flags_in_cc) - cymunk = self.get_recipe('cymunk', self.ctx).get_build_dir(arch.arch) - env['PYTHONPATH'] = join(cymunk, 'cymunk', 'python') - kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch) - kivent = self.get_recipe('kivent_core', - self.ctx).get_build_dir(arch.arch, sub=True) - env['CYTHONPATH'] = ':'.join((kivy, cymunk, kivent)) - return env - - def prepare_build_dir(self, arch): - '''No need to prepare, we'll use kivent_core''' - return - - def get_build_dir(self, arch): - builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch) - return join(builddir, 'modules', 'cymunk') - - -recipe = KiventCymunkRecipe() diff --git a/p4a/pythonforandroid/recipes/kivent_particles/__init__.py b/p4a/pythonforandroid/recipes/kivent_particles/__init__.py deleted file mode 100644 index ce82ea0c..00000000 --- a/p4a/pythonforandroid/recipes/kivent_particles/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from pythonforandroid.toolchain import CythonRecipe -from os.path import join - - -class KiventParticlesRecipe(CythonRecipe): - name = 'kivent_particles' - - depends = ['kivent_core'] - - subbuilddir = False - - def get_recipe_env(self, arch, with_flags_in_cc=True): - env = super(KiventParticlesRecipe, self).get_recipe_env( - arch, with_flags_in_cc=with_flags_in_cc) - kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch) - kivent = self.get_recipe('kivent_core', - self.ctx).get_build_dir(arch.arch, sub=True) - env['CYTHONPATH'] = ':'.join((kivy, kivent)) - return env - - def prepare_build_dir(self, arch): - '''No need to prepare, we'll use kivent_core''' - return - - def get_build_dir(self, arch): - builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch) - return join(builddir, 'modules', 'particles') - - -recipe = KiventParticlesRecipe() diff --git a/p4a/pythonforandroid/recipes/kivent_polygen/__init__.py b/p4a/pythonforandroid/recipes/kivent_polygen/__init__.py deleted file mode 100644 index 9cbd1981..00000000 --- a/p4a/pythonforandroid/recipes/kivent_polygen/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from pythonforandroid.toolchain import CythonRecipe -from os.path import join - - -class KiventPolygenRecipe(CythonRecipe): - name = 'kivent_polygen' - - depends = ['kivent_core'] - - subbuilddir = False - - def get_recipe_env(self, arch, with_flags_in_cc=True): - env = super(KiventPolygenRecipe, self).get_recipe_env( - arch, with_flags_in_cc=with_flags_in_cc) - kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch) - kivent = self.get_recipe('kivent_core', - self.ctx).get_build_dir(arch.arch, sub=True) - env['CYTHONPATH'] = ':'.join((kivy, kivent)) - return env - - def prepare_build_dir(self, arch): - '''No need to prepare, we'll use kivent_core''' - return - - def get_build_dir(self, arch): - builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch) - return join(builddir, 'modules', 'polygen') - - -recipe = KiventPolygenRecipe() diff --git a/p4a/pythonforandroid/recipes/kivy/__init__.py b/p4a/pythonforandroid/recipes/kivy/__init__.py index 975c8553..d21107f0 100644 --- a/p4a/pythonforandroid/recipes/kivy/__init__.py +++ b/p4a/pythonforandroid/recipes/kivy/__init__.py @@ -1,19 +1,18 @@ - -from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, ArchARM -from os.path import exists, join +from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.toolchain import current_directory, shprint +from os.path import exists, join, basename import sh import glob class KivyRecipe(CythonRecipe): - version = '1.10.0' + # post kivy==1.10.1, `fixes SDL2 image loading (jpg)` + version = 'c4d6894' url = 'https://github.com/kivy/kivy/archive/{version}.zip' name = 'kivy' depends = [('sdl2', 'pygame'), 'pyjnius'] - # patches = ['setargv.patch'] - def cythonize_build(self, env, build_dir='.'): super(KivyRecipe, self).cythonize_build(env, build_dir=build_dir) @@ -30,6 +29,14 @@ class KivyRecipe(CythonRecipe): shprint(sh.cp, '-r', join('kivy', 'include'), join(dirn, 'kivy')) + def cythonize_file(self, env, build_dir, filename): + # We can ignore a few files that aren't important to the + # android build, and may not work on Android anyway + do_not_cythonize = ['window_x11.pyx', ] + if basename(filename) in do_not_cythonize: + return + super(KivyRecipe, self).cythonize_file(env, build_dir, filename) + def get_recipe_env(self, arch): env = super(KivyRecipe, self).get_recipe_env(arch) if 'sdl2' in self.ctx.recipe_build_order: @@ -44,4 +51,5 @@ class KivyRecipe(CythonRecipe): return env + recipe = KivyRecipe() diff --git a/p4a/pythonforandroid/recipes/kiwisolver/__init__.py b/p4a/pythonforandroid/recipes/kiwisolver/__init__.py index d7b5e275..ae6fa175 100644 --- a/p4a/pythonforandroid/recipes/kiwisolver/__init__.py +++ b/p4a/pythonforandroid/recipes/kiwisolver/__init__.py @@ -1,9 +1,11 @@ from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe + class KiwiSolverRecipe(CppCompiledComponentsPythonRecipe): site_packages_name = 'kiwisolver' version = '0.1.3' url = 'https://github.com/nucleic/kiwi/archive/master.zip' - depends = ['python2', 'setuptools'] - + depends = ['setuptools'] + + recipe = KiwiSolverRecipe() diff --git a/p4a/pythonforandroid/recipes/leveldb/__init__.py b/p4a/pythonforandroid/recipes/leveldb/__init__.py index 92dc4fdc..e7ebe716 100644 --- a/p4a/pythonforandroid/recipes/leveldb/__init__.py +++ b/p4a/pythonforandroid/recipes/leveldb/__init__.py @@ -1,7 +1,8 @@ from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory -from os.path import join, exists +from os.path import join import sh + class LevelDBRecipe(Recipe): version = '1.18' url = 'https://github.com/google/leveldb/archive/v{version}.tar.gz' @@ -42,4 +43,5 @@ class LevelDBRecipe(Recipe): ' -lgnustl_shared' return env + recipe = LevelDBRecipe() diff --git a/p4a/pythonforandroid/recipes/libcurl/__init__.py b/p4a/pythonforandroid/recipes/libcurl/__init__.py new file mode 100644 index 00000000..e8cc8604 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libcurl/__init__.py @@ -0,0 +1,40 @@ +import sh +from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory +from os.path import exists, join +from multiprocessing import cpu_count + + +class LibcurlRecipe(Recipe): + version = '7.55.1' + url = 'https://curl.haxx.se/download/curl-7.55.1.tar.gz' + depends = ['openssl'] + + def should_build(self, arch): + super(LibcurlRecipe, self).should_build(arch) + return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libcurl.so')) + + def build_arch(self, arch): + super(LibcurlRecipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + + r = self.get_recipe('openssl', self.ctx) + openssl_dir = r.get_build_dir(arch.arch) + + with current_directory(self.get_build_dir(arch.arch)): + dst_dir = join(self.get_build_dir(arch.arch), 'dist') + shprint( + sh.Command('./configure'), + '--host=arm-linux-androideabi', + '--enable-shared', + '--with-ssl={}'.format(openssl_dir), + '--prefix={}'.format(dst_dir), + _env=env) + shprint(sh.make, '-j', str(cpu_count()), _env=env) + shprint(sh.make, 'install', _env=env) + shutil.copyfile('{}/lib/libcurl.so'.format(dst_dir), + join( + self.ctx.get_libs_dir(arch.arch), + 'libcurl.so')) + + +recipe = LibcurlRecipe() diff --git a/p4a/pythonforandroid/recipes/libexpat/__init__.py b/p4a/pythonforandroid/recipes/libexpat/__init__.py new file mode 100644 index 00000000..ecf5265d --- /dev/null +++ b/p4a/pythonforandroid/recipes/libexpat/__init__.py @@ -0,0 +1,38 @@ + +import sh +from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory +from os.path import exists, join +from multiprocessing import cpu_count + + +class LibexpatRecipe(Recipe): + version = 'master' + url = 'https://github.com/libexpat/libexpat/archive/{version}.zip' + depends = [] + + def should_build(self, arch): + super(LibexpatRecipe, self).should_build(arch) + return not exists( + join(self.ctx.get_libs_dir(arch.arch), 'libexpat.so')) + + def build_arch(self, arch): + super(LibexpatRecipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + with current_directory(join(self.get_build_dir(arch.arch), 'expat')): + dst_dir = join(self.get_build_dir(arch.arch), 'dist') + shprint(sh.Command('./buildconf.sh'), _env=env) + shprint( + sh.Command('./configure'), + '--host=arm-linux-androideabi', + '--enable-shared', + '--without-xmlwf', + '--prefix={}'.format(dst_dir), + _env=env) + shprint(sh.make, '-j', str(cpu_count()), _env=env) + shprint(sh.make, 'install', _env=env) + shutil.copyfile( + '{}/lib/libexpat.so'.format(dst_dir), + join(self.ctx.get_libs_dir(arch.arch), 'libexpat.so')) + + +recipe = LibexpatRecipe() diff --git a/p4a/pythonforandroid/recipes/libffi/__init__.py b/p4a/pythonforandroid/recipes/libffi/__init__.py index 1f216b96..31ed9c69 100644 --- a/p4a/pythonforandroid/recipes/libffi/__init__.py +++ b/p4a/pythonforandroid/recipes/libffi/__init__.py @@ -1,71 +1,53 @@ +from os.path import exists, join +from multiprocessing import cpu_count from pythonforandroid.recipe import Recipe from pythonforandroid.logger import shprint -from pythonforandroid.util import current_directory -from os.path import exists, join +from pythonforandroid.util import current_directory, ensure_dir import sh -import glob class LibffiRecipe(Recipe): - name = 'libffi' - version = 'v3.2.1' - url = 'https://github.com/atgreen/libffi/archive/{version}.zip' + """ + Requires additional system dependencies on Ubuntu: + - `automake` for the `aclocal` binary + - `autoconf` for the `autoreconf` binary + - `libltdl-dev` which defines the `LT_SYS_SYMBOL_USCORE` macro + """ + name = 'libffi' + version = '3.2.1' + url = 'https://github.com/libffi/libffi/archive/v{version}.tar.gz' - patches = ['remove-version-info.patch'] + patches = ['remove-version-info.patch', + # This patch below is already included into libffi's master + # branch and included in the pre-release 3.3rc0...so we should + # remove this when we update the version number for libffi + 'fix-includedir.patch'] - def get_host(self, arch): - with current_directory(self.get_build_dir(arch.arch)): - host = None - with open('Makefile') as f: - for line in f: - if line.startswith('host = '): - host = line.strip()[7:] - break + def should_build(self, arch): + return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so')) - if not host or not exists(host): - raise RuntimeError('failed to find build output! ({})' - .format(host)) - - return host + def build_arch(self, arch): + env = self.get_recipe_env(arch) + with current_directory(self.get_build_dir(arch.arch)): + if not exists('configure'): + shprint(sh.Command('./autogen.sh'), _env=env) + shprint(sh.Command('autoreconf'), '-vif', _env=env) + shprint(sh.Command('./configure'), + '--host=' + arch.command_prefix, + '--prefix=' + self.get_build_dir(arch.arch), + '--disable-builddir', + '--enable-shared', _env=env) - def should_build(self, arch): - # return not bool(glob.glob(join(self.ctx.get_libs_dir(arch.arch), - # 'libffi.so*'))) - return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so')) - # return not exists(join(self.ctx.get_python_install_dir(), 'lib', - # 'libffi.so')) + shprint(sh.make, '-j', str(cpu_count()), 'libffi.la', _env=env) - def build_arch(self, arch): - env = self.get_recipe_env(arch) - with current_directory(self.get_build_dir(arch.arch)): - if not exists('configure'): - shprint(sh.Command('./autogen.sh'), _env=env) - shprint(sh.Command('autoreconf'), '-vif', _env=env) - shprint(sh.Command('./configure'), '--host=' + arch.toolchain_prefix, - '--prefix=' + self.ctx.get_python_install_dir(), - '--enable-shared', _env=env) - shprint(sh.make, '-j5', 'libffi.la', _env=env) + host_build = self.get_build_dir(arch.arch) + ensure_dir(self.ctx.get_libs_dir(arch.arch)) + shprint(sh.cp, + join(host_build, '.libs', 'libffi.so'), + self.ctx.get_libs_dir(arch.arch)) - - # dlname = None - # with open(join(host, 'libffi.la')) as f: - # for line in f: - # if line.startswith('dlname='): - # dlname = line.strip()[8:-1] - # break - # - # if not dlname or not exists(join(host, '.libs', dlname)): - # raise RuntimeError('failed to locate shared object! ({})' - # .format(dlname)) - - # shprint(sh.sed, '-i', 's/^dlname=.*$/dlname=\'libffi.so\'/', join(host, 'libffi.la')) - - shprint(sh.cp, '-t', self.ctx.get_libs_dir(arch.arch), - join(self.get_host(arch), '.libs', 'libffi.so')) #, - # join(host, 'libffi.la')) - - def get_include_dirs(self, arch): - return [join(self.get_build_dir(arch.arch), self.get_host(arch), 'include')] + def get_include_dirs(self, arch): + return [join(self.get_build_dir(arch.arch), 'include')] recipe = LibffiRecipe() diff --git a/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch b/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch new file mode 100644 index 00000000..0dc35c70 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch @@ -0,0 +1,34 @@ +From 982b89c01aca99c7bc229914fc1521f96930919b Mon Sep 17 00:00:00 2001 +From: Yen Chi Hsuan +Date: Sun, 13 Nov 2016 19:17:19 +0800 +Subject: [PATCH] Install public headers in the standard path + +--- + include/Makefile.am | 3 +-- + libffi.pc.in | 2 +- + 2 files changed, 2 insertions(+), 3 deletions(-) + +diff --git a/include/Makefile.am b/include/Makefile.am +index bb241e88..c59df9fb 100644 +--- a/include/Makefile.am ++++ b/include/Makefile.am +@@ -6,5 +6,4 @@ DISTCLEANFILES=ffitarget.h + noinst_HEADERS=ffi_common.h ffi_cfi.h + EXTRA_DIST=ffi.h.in + +-includesdir = $(libdir)/@PACKAGE_NAME@-@PACKAGE_VERSION@/include +-nodist_includes_HEADERS = ffi.h ffitarget.h ++nodist_include_HEADERS = ffi.h ffitarget.h +diff --git a/libffi.pc.in b/libffi.pc.in +index edf6fde5..6fad83b4 100644 +--- a/libffi.pc.in ++++ b/libffi.pc.in +@@ -2,7 +2,7 @@ prefix=@prefix@ + exec_prefix=@exec_prefix@ + libdir=@libdir@ + toolexeclibdir=@toolexeclibdir@ +-includedir=${libdir}/@PACKAGE_NAME@-@PACKAGE_VERSION@/include ++includedir=@includedir@ + + Name: @PACKAGE_NAME@ + Description: Library supporting Foreign Function Interfaces diff --git a/p4a/pythonforandroid/recipes/libgeos/__init__.py b/p4a/pythonforandroid/recipes/libgeos/__init__.py index 20f35397..30786f8e 100644 --- a/p4a/pythonforandroid/recipes/libgeos/__init__.py +++ b/p4a/pythonforandroid/recipes/libgeos/__init__.py @@ -1,14 +1,14 @@ from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory -from pythonforandroid.util import ensure_dir -from os.path import exists, join, abspath +from os.path import exists, join import sh from multiprocessing import cpu_count + class LibgeosRecipe(Recipe): version = '3.5' - #url = 'http://download.osgeo.org/geos/geos-{version}.tar.bz2' + # url = 'http://download.osgeo.org/geos/geos-{version}.tar.bz2' url = 'https://github.com/libgeos/libgeos/archive/svn-{version}.zip' - depends = ['python2'] + depends = [] def should_build(self, arch): super(LibgeosRecipe, self).should_build(arch) @@ -17,17 +17,17 @@ class LibgeosRecipe(Recipe): def build_arch(self, arch): super(LibgeosRecipe, self).build_arch(arch) env = self.get_recipe_env(arch) - + with current_directory(self.get_build_dir(arch.arch)): - dst_dir = join(self.get_build_dir(arch.arch),'dist') + dst_dir = join(self.get_build_dir(arch.arch), 'dist') bash = sh.Command('bash') print("If this fails make sure you have autoconf and libtool installed") - shprint(bash,'autogen.sh') # Requires autoconf and libtool - shprint(bash, 'configure', '--host=arm-linux-androideabi', '--enable-shared','--prefix={}'.format(dst_dir), _env=env) - shprint(sh.make,'-j',str(cpu_count()),_env=env) - shprint(sh.make,'install',_env=env) + shprint(bash, 'autogen.sh') # Requires autoconf and libtool + shprint(bash, 'configure', '--host=arm-linux-androideabi', '--enable-shared', '--prefix={}'.format(dst_dir), _env=env) + shprint(sh.make, '-j', str(cpu_count()), _env=env) + shprint(sh.make, 'install', _env=env) shutil.copyfile('{}/lib/libgeos_c.so'.format(dst_dir), join(self.ctx.get_libs_dir(arch.arch), 'libgeos_c.so')) - + def get_recipe_env(self, arch): env = super(LibgeosRecipe, self).get_recipe_env(arch) env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/4.8/include'.format(self.ctx.ndk_dir) @@ -40,5 +40,5 @@ class LibgeosRecipe(Recipe): self.ctx.ndk_dir, arch) return env -recipe = LibgeosRecipe() +recipe = LibgeosRecipe() diff --git a/p4a/pythonforandroid/recipes/libglob/__init__.py b/p4a/pythonforandroid/recipes/libglob/__init__.py new file mode 100644 index 00000000..e0fccfec --- /dev/null +++ b/p4a/pythonforandroid/recipes/libglob/__init__.py @@ -0,0 +1,66 @@ +""" + android libglob + available via '-lglob' LDFLAG +""" +from os.path import exists, join +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from pythonforandroid.toolchain import current_directory +from pythonforandroid.logger import info, shprint +import sh + + +class LibGlobRecipe(CompiledComponentsPythonRecipe): + """Make a glob.h and glob.so for the python_install_dir()""" + version = '0.0.1' + url = None + # + # glob.h and glob.c extracted from + # https://github.com/white-gecko/TokyoCabinet, e.g.: + # https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.h + # https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.c + # and pushed in via patch + name = 'libglob' + + depends = [('hostpython2', 'hostpython3')] + patches = ['glob.patch'] + + def should_build(self, arch): + """It's faster to build than check""" + return True + + def prebuild_arch(self, arch): + """Make the build and target directories""" + path = self.get_build_dir(arch.arch) + if not exists(path): + info("creating {}".format(path)) + shprint(sh.mkdir, '-p', path) + + def build_arch(self, arch): + """simple shared compile""" + env = self.get_recipe_env(arch, with_flags_in_cc=False) + for path in ( + self.get_build_dir(arch.arch), + join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'), + join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')): + if not exists(path): + info("creating {}".format(path)) + shprint(sh.mkdir, '-p', path) + cli = env['CC'].split()[0] + # makes sure first CC command is the compiler rather than ccache, refs: + # https://github.com/kivy/python-for-android/issues/1399 + if 'ccache' in cli: + cli = env['CC'].split()[1] + cc = sh.Command(cli) + + with current_directory(self.get_build_dir(arch.arch)): + cflags = env['CFLAGS'].split() + cflags.extend(['-I.', '-c', '-l.', 'glob.c', '-I.']) + shprint(cc, *cflags, _env=env) + cflags = env['CFLAGS'].split() + cflags.extend(['-shared', '-I.', 'glob.o', '-o', 'libglob.so']) + cflags.extend(env['LDFLAGS'].split()) + shprint(cc, *cflags, _env=env) + shprint(sh.cp, 'libglob.so', join(self.ctx.libs_dir, arch.arch)) + + +recipe = LibGlobRecipe() diff --git a/p4a/pythonforandroid/recipes/libglob/glob.patch b/p4a/pythonforandroid/recipes/libglob/glob.patch new file mode 100644 index 00000000..c7fe8173 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libglob/glob.patch @@ -0,0 +1,1016 @@ +diff -Nur /tmp/x/glob.c libglob/glob.c +--- /tmp/x/glob.c 1969-12-31 19:00:00.000000000 -0500 ++++ libglob/glob.c 2017-08-19 15:23:19.910414868 -0400 +@@ -0,0 +1,906 @@ ++/* ++ * Natanael Arndt, 2011: removed collate.h dependencies ++ * (my changes are trivial) ++ * ++ * Copyright (c) 1989, 1993 ++ * The Regents of the University of California. All rights reserved. ++ * ++ * This code is derived from software contributed to Berkeley by ++ * Guido van Rossum. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 4. Neither the name of the University nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#if defined(LIBC_SCCS) && !defined(lint) ++static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; ++#endif /* LIBC_SCCS and not lint */ ++#include ++__FBSDID("$FreeBSD$"); ++ ++/* ++ * glob(3) -- a superset of the one defined in POSIX 1003.2. ++ * ++ * The [!...] convention to negate a range is supported (SysV, Posix, ksh). ++ * ++ * Optional extra services, controlled by flags not defined by POSIX: ++ * ++ * GLOB_QUOTE: ++ * Escaping convention: \ inhibits any special meaning the following ++ * character might have (except \ at end of string is retained). ++ * GLOB_MAGCHAR: ++ * Set in gl_flags if pattern contained a globbing character. ++ * GLOB_NOMAGIC: ++ * Same as GLOB_NOCHECK, but it will only append pattern if it did ++ * not contain any magic characters. [Used in csh style globbing] ++ * GLOB_ALTDIRFUNC: ++ * Use alternately specified directory access functions. ++ * GLOB_TILDE: ++ * expand ~user/foo to the /home/dir/of/user/foo ++ * GLOB_BRACE: ++ * expand {1,2}{a,b} to 1a 1b 2a 2b ++ * gl_matchc: ++ * Number of matches in the current invocation of glob. ++ */ ++ ++/* ++ * Some notes on multibyte character support: ++ * 1. Patterns with illegal byte sequences match nothing - even if ++ * GLOB_NOCHECK is specified. ++ * 2. Illegal byte sequences in filenames are handled by treating them as ++ * single-byte characters with a value of the first byte of the sequence ++ * cast to wchar_t. ++ * 3. State-dependent encodings are not currently supported. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DOLLAR '$' ++#define DOT '.' ++#define EOS '\0' ++#define LBRACKET '[' ++#define NOT '!' ++#define QUESTION '?' ++#define QUOTE '\\' ++#define RANGE '-' ++#define RBRACKET ']' ++#define SEP '/' ++#define STAR '*' ++#define TILDE '~' ++#define UNDERSCORE '_' ++#define LBRACE '{' ++#define RBRACE '}' ++#define SLASH '/' ++#define COMMA ',' ++ ++#ifndef DEBUG ++ ++#define M_QUOTE 0x8000000000ULL ++#define M_PROTECT 0x4000000000ULL ++#define M_MASK 0xffffffffffULL ++#define M_CHAR 0x00ffffffffULL ++ ++typedef uint_fast64_t Char; ++ ++#else ++ ++#define M_QUOTE 0x80 ++#define M_PROTECT 0x40 ++#define M_MASK 0xff ++#define M_CHAR 0x7f ++ ++typedef char Char; ++ ++#endif ++ ++ ++#define CHAR(c) ((Char)((c)&M_CHAR)) ++#define META(c) ((Char)((c)|M_QUOTE)) ++#define M_ALL META('*') ++#define M_END META(']') ++#define M_NOT META('!') ++#define M_ONE META('?') ++#define M_RNG META('-') ++#define M_SET META('[') ++#define ismeta(c) (((c)&M_QUOTE) != 0) ++ ++ ++static int compare(const void *, const void *); ++static int g_Ctoc(const Char *, char *, size_t); ++static int g_lstat(Char *, struct stat *, glob_t *); ++static DIR *g_opendir(Char *, glob_t *); ++static const Char *g_strchr(const Char *, wchar_t); ++#ifdef notdef ++static Char *g_strcat(Char *, const Char *); ++#endif ++static int g_stat(Char *, struct stat *, glob_t *); ++static int glob0(const Char *, glob_t *, size_t *); ++static int glob1(Char *, glob_t *, size_t *); ++static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *); ++static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *); ++static int globextend(const Char *, glob_t *, size_t *); ++static const Char * ++ globtilde(const Char *, Char *, size_t, glob_t *); ++static int globexp1(const Char *, glob_t *, size_t *); ++static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *); ++static int match(Char *, Char *, Char *); ++#ifdef DEBUG ++static void qprintf(const char *, Char *); ++#endif ++ ++int ++glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) ++{ ++ const char *patnext; ++ size_t limit; ++ Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; ++ mbstate_t mbs; ++ wchar_t wc; ++ size_t clen; ++ ++ patnext = pattern; ++ if (!(flags & GLOB_APPEND)) { ++ pglob->gl_pathc = 0; ++ pglob->gl_pathv = NULL; ++ if (!(flags & GLOB_DOOFFS)) ++ pglob->gl_offs = 0; ++ } ++ if (flags & GLOB_LIMIT) { ++ limit = pglob->gl_matchc; ++ if (limit == 0) ++ limit = ARG_MAX; ++ } else ++ limit = 0; ++ pglob->gl_flags = flags & ~GLOB_MAGCHAR; ++ pglob->gl_errfunc = errfunc; ++ pglob->gl_matchc = 0; ++ ++ bufnext = patbuf; ++ bufend = bufnext + MAXPATHLEN - 1; ++ if (flags & GLOB_NOESCAPE) { ++ memset(&mbs, 0, sizeof(mbs)); ++ while (bufend - bufnext >= MB_CUR_MAX) { ++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) ++ return (GLOB_NOMATCH); ++ else if (clen == 0) ++ break; ++ *bufnext++ = wc; ++ patnext += clen; ++ } ++ } else { ++ /* Protect the quoted characters. */ ++ memset(&mbs, 0, sizeof(mbs)); ++ while (bufend - bufnext >= MB_CUR_MAX) { ++ if (*patnext == QUOTE) { ++ if (*++patnext == EOS) { ++ *bufnext++ = QUOTE | M_PROTECT; ++ continue; ++ } ++ prot = M_PROTECT; ++ } else ++ prot = 0; ++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) ++ return (GLOB_NOMATCH); ++ else if (clen == 0) ++ break; ++ *bufnext++ = wc | prot; ++ patnext += clen; ++ } ++ } ++ *bufnext = EOS; ++ ++ if (flags & GLOB_BRACE) ++ return globexp1(patbuf, pglob, &limit); ++ else ++ return glob0(patbuf, pglob, &limit); ++} ++ ++/* ++ * Expand recursively a glob {} pattern. When there is no more expansion ++ * invoke the standard globbing routine to glob the rest of the magic ++ * characters ++ */ ++static int ++globexp1(const Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ const Char* ptr = pattern; ++ int rv; ++ ++ /* Protect a single {}, for find(1), like csh */ ++ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) ++ return glob0(pattern, pglob, limit); ++ ++ while ((ptr = g_strchr(ptr, LBRACE)) != NULL) ++ if (!globexp2(ptr, pattern, pglob, &rv, limit)) ++ return rv; ++ ++ return glob0(pattern, pglob, limit); ++} ++ ++ ++/* ++ * Recursive brace globbing helper. Tries to expand a single brace. ++ * If it succeeds then it invokes globexp1 with the new pattern. ++ * If it fails then it tries to glob the rest of the pattern and returns. ++ */ ++static int ++globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit) ++{ ++ int i; ++ Char *lm, *ls; ++ const Char *pe, *pm, *pm1, *pl; ++ Char patbuf[MAXPATHLEN]; ++ ++ /* copy part up to the brace */ ++ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) ++ continue; ++ *lm = EOS; ++ ls = lm; ++ ++ /* Find the balanced brace */ ++ for (i = 0, pe = ++ptr; *pe; pe++) ++ if (*pe == LBRACKET) { ++ /* Ignore everything between [] */ ++ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) ++ continue; ++ if (*pe == EOS) { ++ /* ++ * We could not find a matching RBRACKET. ++ * Ignore and just look for RBRACE ++ */ ++ pe = pm; ++ } ++ } ++ else if (*pe == LBRACE) ++ i++; ++ else if (*pe == RBRACE) { ++ if (i == 0) ++ break; ++ i--; ++ } ++ ++ /* Non matching braces; just glob the pattern */ ++ if (i != 0 || *pe == EOS) { ++ *rv = glob0(patbuf, pglob, limit); ++ return 0; ++ } ++ ++ for (i = 0, pl = pm = ptr; pm <= pe; pm++) ++ switch (*pm) { ++ case LBRACKET: ++ /* Ignore everything between [] */ ++ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) ++ continue; ++ if (*pm == EOS) { ++ /* ++ * We could not find a matching RBRACKET. ++ * Ignore and just look for RBRACE ++ */ ++ pm = pm1; ++ } ++ break; ++ ++ case LBRACE: ++ i++; ++ break; ++ ++ case RBRACE: ++ if (i) { ++ i--; ++ break; ++ } ++ /* FALLTHROUGH */ ++ case COMMA: ++ if (i && *pm == COMMA) ++ break; ++ else { ++ /* Append the current string */ ++ for (lm = ls; (pl < pm); *lm++ = *pl++) ++ continue; ++ /* ++ * Append the rest of the pattern after the ++ * closing brace ++ */ ++ for (pl = pe + 1; (*lm++ = *pl++) != EOS;) ++ continue; ++ ++ /* Expand the current pattern */ ++#ifdef DEBUG ++ qprintf("globexp2:", patbuf); ++#endif ++ *rv = globexp1(patbuf, pglob, limit); ++ ++ /* move after the comma, to the next string */ ++ pl = pm + 1; ++ } ++ break; ++ ++ default: ++ break; ++ } ++ *rv = 0; ++ return 0; ++} ++ ++ ++ ++/* ++ * expand tilde from the passwd file. ++ */ ++static const Char * ++globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) ++{ ++ struct passwd *pwd; ++ char *h; ++ const Char *p; ++ Char *b, *eb; ++ ++ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) ++ return pattern; ++ ++ /* ++ * Copy up to the end of the string or / ++ */ ++ eb = &patbuf[patbuf_len - 1]; ++ for (p = pattern + 1, h = (char *) patbuf; ++ h < (char *)eb && *p && *p != SLASH; *h++ = *p++) ++ continue; ++ ++ *h = EOS; ++ ++ if (((char *) patbuf)[0] == EOS) { ++ /* ++ * handle a plain ~ or ~/ by expanding $HOME first (iff ++ * we're not running setuid or setgid) and then trying ++ * the password file ++ */ ++ if (issetugid() != 0 || ++ (h = getenv("HOME")) == NULL) { ++ if (((h = getlogin()) != NULL && ++ (pwd = getpwnam(h)) != NULL) || ++ (pwd = getpwuid(getuid())) != NULL) ++ h = pwd->pw_dir; ++ else ++ return pattern; ++ } ++ } ++ else { ++ /* ++ * Expand a ~user ++ */ ++ if ((pwd = getpwnam((char*) patbuf)) == NULL) ++ return pattern; ++ else ++ h = pwd->pw_dir; ++ } ++ ++ /* Copy the home directory */ ++ for (b = patbuf; b < eb && *h; *b++ = *h++) ++ continue; ++ ++ /* Append the rest of the pattern */ ++ while (b < eb && (*b++ = *p++) != EOS) ++ continue; ++ *b = EOS; ++ ++ return patbuf; ++} ++ ++ ++/* ++ * The main glob() routine: compiles the pattern (optionally processing ++ * quotes), calls glob1() to do the real pattern matching, and finally ++ * sorts the list (unless unsorted operation is requested). Returns 0 ++ * if things went well, nonzero if errors occurred. ++ */ ++static int ++glob0(const Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ const Char *qpatnext; ++ int err; ++ size_t oldpathc; ++ Char *bufnext, c, patbuf[MAXPATHLEN]; ++ ++ qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); ++ oldpathc = pglob->gl_pathc; ++ bufnext = patbuf; ++ ++ /* We don't need to check for buffer overflow any more. */ ++ while ((c = *qpatnext++) != EOS) { ++ switch (c) { ++ case LBRACKET: ++ c = *qpatnext; ++ if (c == NOT) ++ ++qpatnext; ++ if (*qpatnext == EOS || ++ g_strchr(qpatnext+1, RBRACKET) == NULL) { ++ *bufnext++ = LBRACKET; ++ if (c == NOT) ++ --qpatnext; ++ break; ++ } ++ *bufnext++ = M_SET; ++ if (c == NOT) ++ *bufnext++ = M_NOT; ++ c = *qpatnext++; ++ do { ++ *bufnext++ = CHAR(c); ++ if (*qpatnext == RANGE && ++ (c = qpatnext[1]) != RBRACKET) { ++ *bufnext++ = M_RNG; ++ *bufnext++ = CHAR(c); ++ qpatnext += 2; ++ } ++ } while ((c = *qpatnext++) != RBRACKET); ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ *bufnext++ = M_END; ++ break; ++ case QUESTION: ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ *bufnext++ = M_ONE; ++ break; ++ case STAR: ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ /* collapse adjacent stars to one, ++ * to avoid exponential behavior ++ */ ++ if (bufnext == patbuf || bufnext[-1] != M_ALL) ++ *bufnext++ = M_ALL; ++ break; ++ default: ++ *bufnext++ = CHAR(c); ++ break; ++ } ++ } ++ *bufnext = EOS; ++#ifdef DEBUG ++ qprintf("glob0:", patbuf); ++#endif ++ ++ if ((err = glob1(patbuf, pglob, limit)) != 0) ++ return(err); ++ ++ /* ++ * If there was no match we are going to append the pattern ++ * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified ++ * and the pattern did not contain any magic characters ++ * GLOB_NOMAGIC is there just for compatibility with csh. ++ */ ++ if (pglob->gl_pathc == oldpathc) { ++ if (((pglob->gl_flags & GLOB_NOCHECK) || ++ ((pglob->gl_flags & GLOB_NOMAGIC) && ++ !(pglob->gl_flags & GLOB_MAGCHAR)))) ++ return(globextend(pattern, pglob, limit)); ++ else ++ return(GLOB_NOMATCH); ++ } ++ if (!(pglob->gl_flags & GLOB_NOSORT)) ++ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc, ++ pglob->gl_pathc - oldpathc, sizeof(char *), compare); ++ return(0); ++} ++ ++static int ++compare(const void *p, const void *q) ++{ ++ return(strcmp(*(char **)p, *(char **)q)); ++} ++ ++static int ++glob1(Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ Char pathbuf[MAXPATHLEN]; ++ ++ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ ++ if (*pattern == EOS) ++ return(0); ++ return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, ++ pattern, pglob, limit)); ++} ++ ++/* ++ * The functions glob2 and glob3 are mutually recursive; there is one level ++ * of recursion for each segment in the pattern that contains one or more ++ * meta characters. ++ */ ++static int ++glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, ++ glob_t *pglob, size_t *limit) ++{ ++ struct stat sb; ++ Char *p, *q; ++ int anymeta; ++ ++ /* ++ * Loop over pattern segments until end of pattern or until ++ * segment with meta character found. ++ */ ++ for (anymeta = 0;;) { ++ if (*pattern == EOS) { /* End of pattern? */ ++ *pathend = EOS; ++ if (g_lstat(pathbuf, &sb, pglob)) ++ return(0); ++ ++ if (((pglob->gl_flags & GLOB_MARK) && ++ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ++ || (S_ISLNK(sb.st_mode) && ++ (g_stat(pathbuf, &sb, pglob) == 0) && ++ S_ISDIR(sb.st_mode)))) { ++ if (pathend + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend++ = SEP; ++ *pathend = EOS; ++ } ++ ++pglob->gl_matchc; ++ return(globextend(pathbuf, pglob, limit)); ++ } ++ ++ /* Find end of next segment, copy tentatively to pathend. */ ++ q = pathend; ++ p = pattern; ++ while (*p != EOS && *p != SEP) { ++ if (ismeta(*p)) ++ anymeta = 1; ++ if (q + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *q++ = *p++; ++ } ++ ++ if (!anymeta) { /* No expansion, do next segment. */ ++ pathend = q; ++ pattern = p; ++ while (*pattern == SEP) { ++ if (pathend + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend++ = *pattern++; ++ } ++ } else /* Need expansion, recurse. */ ++ return(glob3(pathbuf, pathend, pathend_last, pattern, p, ++ pglob, limit)); ++ } ++ /* NOTREACHED */ ++} ++ ++static int ++glob3(Char *pathbuf, Char *pathend, Char *pathend_last, ++ Char *pattern, Char *restpattern, ++ glob_t *pglob, size_t *limit) ++{ ++ struct dirent *dp; ++ DIR *dirp; ++ int err; ++ char buf[MAXPATHLEN]; ++ ++ /* ++ * The readdirfunc declaration can't be prototyped, because it is ++ * assigned, below, to two functions which are prototyped in glob.h ++ * and dirent.h as taking pointers to differently typed opaque ++ * structures. ++ */ ++ struct dirent *(*readdirfunc)(); ++ ++ if (pathend > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend = EOS; ++ errno = 0; ++ ++ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { ++ /* TODO: don't call for ENOENT or ENOTDIR? */ ++ if (pglob->gl_errfunc) { ++ if (g_Ctoc(pathbuf, buf, sizeof(buf))) ++ return (GLOB_ABORTED); ++ if (pglob->gl_errfunc(buf, errno) || ++ pglob->gl_flags & GLOB_ERR) ++ return (GLOB_ABORTED); ++ } ++ return(0); ++ } ++ ++ err = 0; ++ ++ /* Search directory for matching names. */ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ readdirfunc = pglob->gl_readdir; ++ else ++ readdirfunc = readdir; ++ while ((dp = (*readdirfunc)(dirp))) { ++ char *sc; ++ Char *dc; ++ wchar_t wc; ++ size_t clen; ++ mbstate_t mbs; ++ ++ /* Initial DOT must be matched literally. */ ++ if (dp->d_name[0] == DOT && *pattern != DOT) ++ continue; ++ memset(&mbs, 0, sizeof(mbs)); ++ dc = pathend; ++ sc = dp->d_name; ++ while (dc < pathend_last) { ++ clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) { ++ wc = *sc; ++ clen = 1; ++ memset(&mbs, 0, sizeof(mbs)); ++ } ++ if ((*dc++ = wc) == EOS) ++ break; ++ sc += clen; ++ } ++ if (!match(pathend, pattern, restpattern)) { ++ *pathend = EOS; ++ continue; ++ } ++ err = glob2(pathbuf, --dc, pathend_last, restpattern, ++ pglob, limit); ++ if (err) ++ break; ++ } ++ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ (*pglob->gl_closedir)(dirp); ++ else ++ closedir(dirp); ++ return(err); ++} ++ ++ ++/* ++ * Extend the gl_pathv member of a glob_t structure to accomodate a new item, ++ * add the new item, and update gl_pathc. ++ * ++ * This assumes the BSD realloc, which only copies the block when its size ++ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic ++ * behavior. ++ * ++ * Return 0 if new item added, error code if memory couldn't be allocated. ++ * ++ * Invariant of the glob_t structure: ++ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and ++ * gl_pathv points to (gl_offs + gl_pathc + 1) items. ++ */ ++static int ++globextend(const Char *path, glob_t *pglob, size_t *limit) ++{ ++ char **pathv; ++ size_t i, newsize, len; ++ char *copy; ++ const Char *p; ++ ++ if (*limit && pglob->gl_pathc > *limit) { ++ errno = 0; ++ return (GLOB_NOSPACE); ++ } ++ ++ newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); ++ pathv = pglob->gl_pathv ? ++ realloc((char *)pglob->gl_pathv, newsize) : ++ malloc(newsize); ++ if (pathv == NULL) { ++ if (pglob->gl_pathv) { ++ free(pglob->gl_pathv); ++ pglob->gl_pathv = NULL; ++ } ++ return(GLOB_NOSPACE); ++ } ++ ++ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { ++ /* first time around -- clear initial gl_offs items */ ++ pathv += pglob->gl_offs; ++ for (i = pglob->gl_offs + 1; --i > 0; ) ++ *--pathv = NULL; ++ } ++ pglob->gl_pathv = pathv; ++ ++ for (p = path; *p++;) ++ continue; ++ len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ ++ if ((copy = malloc(len)) != NULL) { ++ if (g_Ctoc(path, copy, len)) { ++ free(copy); ++ return (GLOB_NOSPACE); ++ } ++ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; ++ } ++ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; ++ return(copy == NULL ? GLOB_NOSPACE : 0); ++} ++ ++/* ++ * pattern matching function for filenames. Each occurrence of the * ++ * pattern causes a recursion level. ++ */ ++static int ++match(Char *name, Char *pat, Char *patend) ++{ ++ int ok, negate_range; ++ Char c, k; ++ ++ while (pat < patend) { ++ c = *pat++; ++ switch (c & M_MASK) { ++ case M_ALL: ++ if (pat == patend) ++ return(1); ++ do ++ if (match(name, pat, patend)) ++ return(1); ++ while (*name++ != EOS); ++ return(0); ++ case M_ONE: ++ if (*name++ == EOS) ++ return(0); ++ break; ++ case M_SET: ++ ok = 0; ++ if ((k = *name++) == EOS) ++ return(0); ++ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) ++ ++pat; ++ while (((c = *pat++) & M_MASK) != M_END) ++ if ((*pat & M_MASK) == M_RNG) { ++ if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1; ++ pat += 2; ++ } else if (c == k) ++ ok = 1; ++ if (ok == negate_range) ++ return(0); ++ break; ++ default: ++ if (*name++ != c) ++ return(0); ++ break; ++ } ++ } ++ return(*name == EOS); ++} ++ ++/* Free allocated data belonging to a glob_t structure. */ ++void ++globfree(glob_t *pglob) ++{ ++ size_t i; ++ char **pp; ++ ++ if (pglob->gl_pathv != NULL) { ++ pp = pglob->gl_pathv + pglob->gl_offs; ++ for (i = pglob->gl_pathc; i--; ++pp) ++ if (*pp) ++ free(*pp); ++ free(pglob->gl_pathv); ++ pglob->gl_pathv = NULL; ++ } ++} ++ ++static DIR * ++g_opendir(Char *str, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (!*str) ++ strcpy(buf, "."); ++ else { ++ if (g_Ctoc(str, buf, sizeof(buf))) ++ return (NULL); ++ } ++ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_opendir)(buf)); ++ ++ return(opendir(buf)); ++} ++ ++static int ++g_lstat(Char *fn, struct stat *sb, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (g_Ctoc(fn, buf, sizeof(buf))) { ++ errno = ENAMETOOLONG; ++ return (-1); ++ } ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_lstat)(buf, sb)); ++ return(lstat(buf, sb)); ++} ++ ++static int ++g_stat(Char *fn, struct stat *sb, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (g_Ctoc(fn, buf, sizeof(buf))) { ++ errno = ENAMETOOLONG; ++ return (-1); ++ } ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_stat)(buf, sb)); ++ return(stat(buf, sb)); ++} ++ ++static const Char * ++g_strchr(const Char *str, wchar_t ch) ++{ ++ ++ do { ++ if (*str == ch) ++ return (str); ++ } while (*str++); ++ return (NULL); ++} ++ ++static int ++g_Ctoc(const Char *str, char *buf, size_t len) ++{ ++ mbstate_t mbs; ++ size_t clen; ++ ++ memset(&mbs, 0, sizeof(mbs)); ++ while (len >= MB_CUR_MAX) { ++ clen = wcrtomb(buf, *str, &mbs); ++ if (clen == (size_t)-1) ++ return (1); ++ if (*str == L'\0') ++ return (0); ++ str++; ++ buf += clen; ++ len -= clen; ++ } ++ return (1); ++} ++ ++#ifdef DEBUG ++static void ++qprintf(const char *str, Char *s) ++{ ++ Char *p; ++ ++ (void)printf("%s:\n", str); ++ for (p = s; *p; p++) ++ (void)printf("%c", CHAR(*p)); ++ (void)printf("\n"); ++ for (p = s; *p; p++) ++ (void)printf("%c", *p & M_PROTECT ? '"' : ' '); ++ (void)printf("\n"); ++ for (p = s; *p; p++) ++ (void)printf("%c", ismeta(*p) ? '_' : ' '); ++ (void)printf("\n"); ++} ++#endif +diff -Nur /tmp/x/glob.h libglob/glob.h +--- /tmp/x/glob.h 1969-12-31 19:00:00.000000000 -0500 ++++ libglob/glob.h 2017-08-19 15:22:18.367109399 -0400 +@@ -0,0 +1,102 @@ ++/* ++ * Copyright (c) 1989, 1993 ++ * The Regents of the University of California. All rights reserved. ++ * ++ * This code is derived from software contributed to Berkeley by ++ * Guido van Rossum. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the name of the University nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ * ++ * @(#)glob.h 8.1 (Berkeley) 6/2/93 ++ * $FreeBSD$ ++ */ ++ ++#ifndef _GLOB_H_ ++#define _GLOB_H_ ++ ++#include ++#include ++#define ARG_MAX 6553 ++ ++#ifndef _SIZE_T_DECLARED ++typedef __size_t size_t; ++#define _SIZE_T_DECLARED ++#endif ++ ++struct stat; ++typedef struct { ++ size_t gl_pathc; /* Count of total paths so far. */ ++ size_t gl_matchc; /* Count of paths matching pattern. */ ++ size_t gl_offs; /* Reserved at beginning of gl_pathv. */ ++ int gl_flags; /* Copy of flags parameter to glob. */ ++ char **gl_pathv; /* List of paths matching pattern. */ ++ /* Copy of errfunc parameter to glob. */ ++ int (*gl_errfunc)(const char *, int); ++ ++ /* ++ * Alternate filesystem access methods for glob; replacement ++ * versions of closedir(3), readdir(3), opendir(3), stat(2) ++ * and lstat(2). ++ */ ++ void (*gl_closedir)(void *); ++ struct dirent *(*gl_readdir)(void *); ++ void *(*gl_opendir)(const char *); ++ int (*gl_lstat)(const char *, struct stat *); ++ int (*gl_stat)(const char *, struct stat *); ++} glob_t; ++ ++/* Believed to have been introduced in 1003.2-1992 */ ++#define GLOB_APPEND 0x0001 /* Append to output from previous call. */ ++#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ ++#define GLOB_ERR 0x0004 /* Return on error. */ ++#define GLOB_MARK 0x0008 /* Append / to matching directories. */ ++#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ ++#define GLOB_NOSORT 0x0020 /* Don't sort. */ ++#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ ++ ++/* Error values returned by glob(3) */ ++#define GLOB_NOSPACE (-1) /* Malloc call failed. */ ++#define GLOB_ABORTED (-2) /* Unignored error. */ ++#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ ++#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */ ++ ++#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ ++#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ ++#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ ++#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ ++#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ ++#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ ++#define GLOB_LIMIT 0x1000 /* limit number of returned paths */ ++ ++/* source compatibility, these are the old names */ ++#define GLOB_MAXPATH GLOB_LIMIT ++#define GLOB_ABEND GLOB_ABORTED ++ ++__BEGIN_DECLS ++int glob(const char *, int, int (*)(const char *, int), glob_t *); ++void globfree(glob_t *); ++__END_DECLS ++ ++#endif /* !_GLOB_H_ */ diff --git a/p4a/pythonforandroid/recipes/libiconv/__init__.py b/p4a/pythonforandroid/recipes/libiconv/__init__.py new file mode 100644 index 00000000..4a646692 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libiconv/__init__.py @@ -0,0 +1,34 @@ +import os +from pythonforandroid.toolchain import shprint, current_directory +from pythonforandroid.recipe import Recipe +from multiprocessing import cpu_count +import sh + + +class LibIconvRecipe(Recipe): + + version = '1.15' + + url = 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{version}.tar.gz' + + patches = ['libiconv-1.15-no-gets.patch'] + + def should_build(self, arch): + return not os.path.exists( + os.path.join(self.ctx.get_libs_dir(arch.arch), 'libiconv.so')) + + def build_arch(self, arch): + super(LibIconvRecipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + with current_directory(self.get_build_dir(arch.arch)): + shprint( + sh.Command('./configure'), + '--host=' + arch.toolchain_prefix, + '--prefix=' + self.ctx.get_python_install_dir(), + _env=env) + shprint(sh.make, '-j' + str(cpu_count()), _env=env) + libs = ['lib/.libs/libiconv.so'] + self.install_libs(arch, *libs) + + +recipe = LibIconvRecipe() diff --git a/p4a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch b/p4a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch new file mode 100644 index 00000000..5bc20b37 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch @@ -0,0 +1,22 @@ +hack until gzip pulls a newer gnulib version + +From 66712c23388e93e5c518ebc8515140fa0c807348 Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Thu, 29 Mar 2012 13:30:41 -0600 +Subject: [PATCH] stdio: don't assume gets any more + +Gnulib intentionally does not have a gets module, and now that C11 +and glibc have dropped it, we should be more proactive about warning +any user on a platform that still has a declaration of this dangerous +interface. + +--- a/srclib/stdio.in.h ++++ b/srclib/stdio.in.h +@@ -744,7 +744,6 @@ _GL_WARN_ON_USE (getline, "getline is un + removed it. */ + #undef gets + #if HAVE_RAW_DECL_GETS && !defined __cplusplus +-_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead"); + #endif + + #if @GNULIB_OBSTACK_PRINTF@ || @GNULIB_OBSTACK_PRINTF_POSIX@ diff --git a/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py b/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py index 0f070ffb..9235ad43 100644 --- a/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py +++ b/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py @@ -6,62 +6,62 @@ from os.path import join class LibmysqlclientRecipe(Recipe): - name = 'libmysqlclient' - version = 'master' - url = 'https://github.com/0x-ff/libmysql-android/archive/{version}.zip' - # version = '5.5.47' - # url = 'http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-{version}.tar.gz' - # - # depends = ['ncurses'] - # + name = 'libmysqlclient' + version = 'master' + url = 'https://github.com/0x-ff/libmysql-android/archive/{version}.zip' + # version = '5.5.47' + # url = 'http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-{version}.tar.gz' + # + # depends = ['ncurses'] + # - # patches = ['add-custom-platform.patch'] + # patches = ['add-custom-platform.patch'] - patches = ['disable-soversion.patch'] + patches = ['disable-soversion.patch'] - def should_build(self, arch): - return not self.has_libs(arch, 'libmysql.so') + def should_build(self, arch): + return not self.has_libs(arch, 'libmysql.so') - def build_arch(self, arch): - env = self.get_recipe_env(arch) - with current_directory(join(self.get_build_dir(arch.arch), 'libmysqlclient')): - shprint(sh.cp, '-t', '.', join(self.get_recipe_dir(), 'p4a.cmake')) - # shprint(sh.mkdir, 'Platform') - # shprint(sh.cp, '-t', 'Platform', join(self.get_recipe_dir(), 'Linux.cmake')) - shprint(sh.rm, '-f', 'CMakeCache.txt') - shprint(sh.cmake, '-G', 'Unix Makefiles', - # '-DCMAKE_MODULE_PATH=' + join(self.get_build_dir(arch.arch), 'libmysqlclient'), - '-DCMAKE_INSTALL_PREFIX=./install', - '-DCMAKE_TOOLCHAIN_FILE=p4a.cmake', _env=env) - shprint(sh.make, _env=env) + def build_arch(self, arch): + env = self.get_recipe_env(arch) + with current_directory(join(self.get_build_dir(arch.arch), 'libmysqlclient')): + shprint(sh.cp, '-t', '.', join(self.get_recipe_dir(), 'p4a.cmake')) + # shprint(sh.mkdir, 'Platform') + # shprint(sh.cp, '-t', 'Platform', join(self.get_recipe_dir(), 'Linux.cmake')) + shprint(sh.rm, '-f', 'CMakeCache.txt') + shprint(sh.cmake, '-G', 'Unix Makefiles', + # '-DCMAKE_MODULE_PATH=' + join(self.get_build_dir(arch.arch), 'libmysqlclient'), + '-DCMAKE_INSTALL_PREFIX=./install', + '-DCMAKE_TOOLCHAIN_FILE=p4a.cmake', _env=env) + shprint(sh.make, _env=env) - self.install_libs(arch, join('libmysql', 'libmysql.so')) + self.install_libs(arch, join('libmysql', 'libmysql.so')) - # def get_recipe_env(self, arch=None): - # env = super(LibmysqlclientRecipe, self).get_recipe_env(arch) - # env['WITHOUT_SERVER'] = 'ON' - # ncurses = self.get_recipe('ncurses', self) - # # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch), - # # 'include') - # env['CURSES_LIBRARY'] = join(self.ctx.get_libs_dir(arch.arch), 'libncurses.so') - # env['CURSES_INCLUDE_PATH'] = join(ncurses.get_build_dir(arch.arch), - # 'include') - # return env - # - # def build_arch(self, arch): - # env = self.get_recipe_env(arch) - # with current_directory(self.get_build_dir(arch.arch)): - # # configure = sh.Command('./configure') - # # TODO: should add openssl as an optional dep and compile support - # # shprint(configure, '--enable-shared', '--enable-assembler', - # # '--enable-thread-safe-client', '--with-innodb', - # # '--without-server', _env=env) - # # shprint(sh.make, _env=env) - # shprint(sh.cmake, '.', '-DCURSES_LIBRARY=' + env['CURSES_LIBRARY'], - # '-DCURSES_INCLUDE_PATH=' + env['CURSES_INCLUDE_PATH'], _env=env) - # shprint(sh.make, _env=env) - # - # self.install_libs(arch, 'libmysqlclient.so') + # def get_recipe_env(self, arch=None): + # env = super(LibmysqlclientRecipe, self).get_recipe_env(arch) + # env['WITHOUT_SERVER'] = 'ON' + # ncurses = self.get_recipe('ncurses', self) + # # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch), + # # 'include') + # env['CURSES_LIBRARY'] = join(self.ctx.get_libs_dir(arch.arch), 'libncurses.so') + # env['CURSES_INCLUDE_PATH'] = join(ncurses.get_build_dir(arch.arch), + # 'include') + # return env + # + # def build_arch(self, arch): + # env = self.get_recipe_env(arch) + # with current_directory(self.get_build_dir(arch.arch)): + # # configure = sh.Command('./configure') + # # TODO: should add openssl as an optional dep and compile support + # # shprint(configure, '--enable-shared', '--enable-assembler', + # # '--enable-thread-safe-client', '--with-innodb', + # # '--without-server', _env=env) + # # shprint(sh.make, _env=env) + # shprint(sh.cmake, '.', '-DCURSES_LIBRARY=' + env['CURSES_LIBRARY'], + # '-DCURSES_INCLUDE_PATH=' + env['CURSES_INCLUDE_PATH'], _env=env) + # shprint(sh.make, _env=env) + # + # self.install_libs(arch, 'libmysqlclient.so') recipe = LibmysqlclientRecipe() diff --git a/p4a/pythonforandroid/recipes/libnacl/__init__.py b/p4a/pythonforandroid/recipes/libnacl/__init__.py index 62fc7bee..3fc5da82 100644 --- a/p4a/pythonforandroid/recipes/libnacl/__init__.py +++ b/p4a/pythonforandroid/recipes/libnacl/__init__.py @@ -1,10 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe + class LibNaClRecipe(PythonRecipe): version = '1.4.4' url = 'https://github.com/saltstack/libnacl/archive/v{version}.tar.gz' - depends = ['hostpython2', 'setuptools'] + depends = [('hostpython2', 'hostpython3'), 'setuptools', 'libsodium'] site_packages_name = 'libnacl' call_hostpython_via_targetpython = False + recipe = LibNaClRecipe() diff --git a/p4a/pythonforandroid/recipes/libogg/__init__.py b/p4a/pythonforandroid/recipes/libogg/__init__.py new file mode 100644 index 00000000..064189eb --- /dev/null +++ b/p4a/pythonforandroid/recipes/libogg/__init__.py @@ -0,0 +1,26 @@ +from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.toolchain import current_directory, shprint +from os.path import join +import sh + + +class OggRecipe(NDKRecipe): + version = '1.3.3' + url = 'http://downloads.xiph.org/releases/ogg/libogg-{version}.tar.gz' + + generated_libraries = ['libogg.so'] + + def build_arch(self, arch): + with current_directory(self.get_build_dir(arch.arch)): + env = self.get_recipe_env(arch) + flags = [ + '--with-sysroot=' + self.ctx.ndk_platform, + '--host=' + arch.toolchain_prefix, + ] + configure = sh.Command('./configure') + shprint(configure, *flags, _env=env) + shprint(sh.make, _env=env) + self.install_libs(arch, join('src', '.libs', 'libogg.so')) + + +recipe = OggRecipe() diff --git a/p4a/pythonforandroid/recipes/libpq/__init__.py b/p4a/pythonforandroid/recipes/libpq/__init__.py index ee018c9e..45c296a2 100644 --- a/p4a/pythonforandroid/recipes/libpq/__init__.py +++ b/p4a/pythonforandroid/recipes/libpq/__init__.py @@ -6,7 +6,7 @@ import os.path class LibpqRecipe(Recipe): version = '9.5.3' url = 'http://ftp.postgresql.org/pub/source/v{version}/postgresql-{version}.tar.bz2' - depends = [('python2', 'python3crystax')] + depends = [] def should_build(self, arch): return not os.path.isfile('{}/libpq.a'.format(self.ctx.get_libs_dir(arch.arch))) @@ -22,4 +22,5 @@ class LibpqRecipe(Recipe): shprint(sh.cp, '-a', 'src/interfaces/libpq/libpq.a', self.ctx.get_libs_dir(arch.arch)) + recipe = LibpqRecipe() diff --git a/p4a/pythonforandroid/recipes/librt/__init__.py b/p4a/pythonforandroid/recipes/librt/__init__.py new file mode 100644 index 00000000..9eb56b3b --- /dev/null +++ b/p4a/pythonforandroid/recipes/librt/__init__.py @@ -0,0 +1,55 @@ +from os import makedirs, remove +from os.path import exists, join +import sh + +from pythonforandroid.recipe import Recipe +from pythonforandroid.logger import shprint + + +class LibRt(Recipe): + ''' + This is a dumb recipe. We may need this because some recipes inserted some + flags `-lrt` without our control, case of: + + - :class:`~pythonforandroid.recipes.gevent.GeventRecipe` + - :class:`~pythonforandroid.recipes.lxml.LXMLRecipe` + + .. note:: the librt doesn't exist in android but it is integrated into + libc, so we create a symbolic link which we will remove when our build + finishes''' + + @property + def libc_path(self): + return join(self.ctx.ndk_platform, 'usr', 'lib', 'libc') + + def build_arch(self, arch): + # Create a temporary folder to add to link path with a fake librt.so: + fake_librt_temp_folder = join( + self.get_build_dir(arch.arch), + "p4a-librt-recipe-tempdir" + ) + if not exists(fake_librt_temp_folder): + makedirs(fake_librt_temp_folder) + + # Set symlinks, and make sure to update them on every build run: + if exists(join(fake_librt_temp_folder, "librt.so")): + remove(join(fake_librt_temp_folder, "librt.so")) + shprint(sh.ln, '-sf', + self.libc_path + '.so', + join(fake_librt_temp_folder, "librt.so"), + ) + if exists(join(fake_librt_temp_folder, "librt.a")): + remove(join(fake_librt_temp_folder, "librt.a")) + shprint(sh.ln, '-sf', + self.libc_path + '.a', + join(fake_librt_temp_folder, "librt.a"), + ) + + # Add folder as -L link option for all recipes if not done yet: + if fake_librt_temp_folder not in arch.extra_global_link_paths: + arch.extra_global_link_paths.append( + fake_librt_temp_folder + ) + + +recipe = LibRt() diff --git a/p4a/pythonforandroid/recipes/libshine/__init__.py b/p4a/pythonforandroid/recipes/libshine/__init__.py index 26df8d83..fe9b5b58 100644 --- a/p4a/pythonforandroid/recipes/libshine/__init__.py +++ b/p4a/pythonforandroid/recipes/libshine/__init__.py @@ -1,14 +1,11 @@ -from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM +from pythonforandroid.toolchain import Recipe, current_directory, shprint from os.path import exists, join, realpath -from os import uname -import glob import sh class LibShineRecipe(Recipe): - version = '20aee967f67abefd065c196eec7ce21adbbe1549' + version = 'c72aba9031bde18a0995e7c01c9b53f2e08a0e46' url = 'https://github.com/toots/shine/archive/{version}.zip' - md5sum = 'bbf1f657e6adccb5e79f59da9ecfac2d' def should_build(self, arch): build_dir = self.get_build_dir(arch.arch) @@ -29,4 +26,5 @@ class LibShineRecipe(Recipe): shprint(sh.make, '-j4', _env=env) shprint(sh.make, 'install', _env=env) + recipe = LibShineRecipe() diff --git a/p4a/pythonforandroid/recipes/libsodium/__init__.py b/p4a/pythonforandroid/recipes/libsodium/__init__.py index 95b2d219..9911e36b 100644 --- a/p4a/pythonforandroid/recipes/libsodium/__init__.py +++ b/p4a/pythonforandroid/recipes/libsodium/__init__.py @@ -2,10 +2,12 @@ from pythonforandroid.toolchain import Recipe, shprint, shutil, current_director from os.path import exists, join import sh + class LibsodiumRecipe(Recipe): - version = '1.0.8' + version = '1.0.16' url = 'https://github.com/jedisct1/libsodium/releases/download/{version}/libsodium-{version}.tar.gz' - depends = ['python2'] + depends = [] + patches = ['size_max_fix.patch'] def should_build(self, arch): super(LibsodiumRecipe, self).should_build(arch) @@ -16,7 +18,7 @@ class LibsodiumRecipe(Recipe): env = self.get_recipe_env(arch) with current_directory(self.get_build_dir(arch.arch)): bash = sh.Command('bash') - shprint(bash, 'configure', '--enable-minimal', '--disable-soname-versions', '--host=arm-linux-androideabi', '--enable-shared', _env=env) + shprint(bash, 'configure', '--disable-soname-versions', '--host=arm-linux-androideabi', '--enable-shared', _env=env) shprint(sh.make, _env=env) shutil.copyfile('src/libsodium/.libs/libsodium.so', join(self.ctx.get_libs_dir(arch.arch), 'libsodium.so')) @@ -25,4 +27,5 @@ class LibsodiumRecipe(Recipe): env['CFLAGS'] += ' -Os' return env + recipe = LibsodiumRecipe() diff --git a/p4a/pythonforandroid/recipes/libsodium/size_max_fix.patch b/p4a/pythonforandroid/recipes/libsodium/size_max_fix.patch new file mode 100644 index 00000000..c05477c7 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libsodium/size_max_fix.patch @@ -0,0 +1,12 @@ +diff -urN libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h libsodium-1.0.16/src/libsodium/include/sodium/export.h +--- libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h 2017-12-12 00:03:07.000000000 +0100 ++++ libsodium-1.0.16/src/libsodium/include/sodium/export.h 2018-10-31 09:46:06.051189444 +0100 +@@ -47,6 +47,8 @@ + # endif + #endif + ++#include ++ + #define SODIUM_MIN(A, B) ((A) < (B) ? (A) : (B)) + #define SODIUM_SIZE_MAX SODIUM_MIN(UINT64_MAX, SIZE_MAX) + diff --git a/p4a/pythonforandroid/recipes/libtorrent/__init__.py b/p4a/pythonforandroid/recipes/libtorrent/__init__.py index 53621f8a..c73bb029 100644 --- a/p4a/pythonforandroid/recipes/libtorrent/__init__.py +++ b/p4a/pythonforandroid/recipes/libtorrent/__init__.py @@ -1,73 +1,138 @@ from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory -from os.path import join, exists +from multiprocessing import cpu_count +from os.path import join, basename +from os import listdir, walk import sh # This recipe builds libtorrent with Python bindings # It depends on Boost.Build and the source of several Boost libraries present in BOOST_ROOT, # which is all provided by the boost recipe + + +def get_lib_from(search_directory, lib_extension='.so'): + '''Scan directories recursively until find any file with the given + extension. The default extension to search is ``.so``.''' + for root, dirs, files in walk(search_directory): + for file in files: + if file.endswith(lib_extension): + print('get_lib_from: {}\n\t- {}'.format( + search_directory, join(root, file))) + return join(root, file) + return None + + class LibtorrentRecipe(Recipe): - version = '1.0.9' - # Don't forget to change the URL when changing the version - url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-1_0_9.tar.gz' - depends = ['boost', 'python2'] + # Todo: make recipe compatible with all p4a architectures + ''' + .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a + + .. versionchanged:: 0.6.0 + Rewrote recipe to support clang's build and boost 1.68. The following + changes has been made: + + - Bumped version number to 1.2.0 + - added python 3 compatibility + - new system to detect/copy generated libraries + ''' + version = '1_2_0' + url = 'https://github.com/arvidn/libtorrent/archive/libtorrent_{version}.tar.gz' + + depends = ['boost'] opt_depends = ['openssl'] - patches = ['disable-so-version.patch', 'use-soname-python.patch', 'setup-lib-name.patch'] + patches = ['disable-so-version.patch', + 'use-soname-python.patch', + 'setup-lib-name.patch'] + + # libtorrent.so is not included because is not a system library + generated_libraries = [ + 'boost_system', 'boost_python{py_version}', 'torrent_rasterbar'] def should_build(self, arch): - return not ( self.has_libs(arch, 'libboost_python.so', 'libboost_system.so', 'libtorrent_rasterbar.so') - and self.ctx.has_package('libtorrent', arch.arch) ) + python_version = self.ctx.python_recipe.version[:3].replace('.', '') + libs = ['lib' + lib_name.format(py_version=python_version) + + '.so' for lib_name in self.generated_libraries] + return not (self.has_libs(arch, *libs) and + self.ctx.has_package('libtorrent', arch.arch)) def prebuild_arch(self, arch): super(LibtorrentRecipe, self).prebuild_arch(arch) if 'openssl' in recipe.ctx.recipe_build_order: # Patch boost user-config.jam to use openssl - self.get_recipe('boost', self.ctx).apply_patch(join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch) + self.get_recipe('boost', self.ctx).apply_patch( + join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch) def build_arch(self, arch): super(LibtorrentRecipe, self).build_arch(arch) env = self.get_recipe_env(arch) - with current_directory(join(self.get_build_dir(arch.arch), 'bindings/python')): - # Compile libtorrent with boost libraries and python bindings + env['PYTHON_HOST'] = self.ctx.hostpython + + # Define build variables + build_dir = self.get_build_dir(arch.arch) + ctx_libs_dir = self.ctx.get_libs_dir(arch.arch) + encryption = 'openssl' if 'openssl' in recipe.ctx.recipe_build_order else 'built-in' + build_args = [ + '-q', + # '-a', # force build, useful to debug the build + '-j' + str(cpu_count()), + '--debug-configuration', # so we know if our python is detected + # '--deprecated-functions=off', + 'toolset=clang-arm', + 'abi=aapcs', + 'binary-format=elf', + 'cxxflags=-std=c++11', + 'target-os=android', + 'threading=multi', + 'link=shared', + 'boost-link=shared', + 'libtorrent-link=shared', + 'runtime-link=shared', + 'encryption={}'.format('on' if encryption == 'openssl' else 'off'), + 'crypto=' + encryption + ] + crypto_folder = 'encryption-off' + if encryption == 'openssl': + crypto_folder = 'crypto-openssl' + build_args.extend(['openssl-lib=' + env['OPENSSL_BUILD_PATH'], + 'openssl-include=' + env['OPENSSL_INCLUDE'] + ]) + build_args.append('release') + + # Compile libtorrent with boost libraries and python bindings + with current_directory(join(build_dir, 'bindings/python')): b2 = sh.Command(join(env['BOOST_ROOT'], 'b2')) - shprint(b2, - '-q', - '-j5', - 'toolset=gcc-' + env['ARCH'], - 'target-os=android', - 'threading=multi', - 'link=shared', - 'boost-link=shared', - 'boost=source', - 'encryption=openssl' if 'openssl' in recipe.ctx.recipe_build_order else '', - '--prefix=' + env['CROSSHOME'], - 'release' - , _env=env) - # Common build directories - build_subdirs = 'gcc-arm/release/boost-link-shared/boost-source' - if 'openssl' in recipe.ctx.recipe_build_order: - build_subdirs += '/encryption-openssl' - build_subdirs += '/libtorrent-python-pic-on/target-os-android/threading-multi/visibility-hidden' - # Copy the shared libraries into the libs folder - shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/python/build', build_subdirs, 'libboost_python.so'), - join(self.ctx.get_libs_dir(arch.arch), 'libboost_python.so')) - shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/system/build', build_subdirs, 'libboost_system.so'), - join(self.ctx.get_libs_dir(arch.arch), 'libboost_system.so')) - if 'openssl' in recipe.ctx.recipe_build_order: - shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/date_time/build', build_subdirs, 'libboost_date_time.so'), - join(self.ctx.get_libs_dir(arch.arch), 'libboost_date_time.so')) - shutil.copyfile(join(self.get_build_dir(arch.arch), 'bin', build_subdirs, 'libtorrent_rasterbar.so'), - join(self.ctx.get_libs_dir(arch.arch), 'libtorrent_rasterbar.so')) - shutil.copyfile(join(self.get_build_dir(arch.arch), 'bindings/python/bin', build_subdirs, 'libtorrent.so'), + shprint(b2, *build_args, _env=env) + + # Copy only the boost shared libraries into the libs folder. Because + # boost build two boost_python libraries, we force to search the lib + # into the corresponding build path. + b2_build_dir = 'build/clang-linux-arm/release/{encryption}/' \ + 'lt-visibility-hidden/'.format(encryption=crypto_folder) + boost_libs_dir = join(env['BOOST_BUILD_PATH'], 'bin.v2/libs') + for boost_lib in listdir(boost_libs_dir): + lib_path = get_lib_from(join(boost_libs_dir, boost_lib, b2_build_dir)) + if lib_path: + lib_name = basename(lib_path) + shutil.copyfile(lib_path, join(ctx_libs_dir, lib_name)) + + # Copy libtorrent shared libraries into the right places + system_libtorrent = get_lib_from(join(build_dir, 'bin')) + if system_libtorrent: + shutil.copyfile(system_libtorrent, + join(ctx_libs_dir, 'libtorrent_rasterbar.so')) + + python_libtorrent = get_lib_from(join(build_dir, 'bindings/python/bin')) + shutil.copyfile(python_libtorrent, join(self.ctx.get_site_packages_dir(arch.arch), 'libtorrent.so')) def get_recipe_env(self, arch): - env = super(LibtorrentRecipe, self).get_recipe_env(arch) - # Copy environment from boost recipe - env.update(self.get_recipe('boost', self.ctx).get_recipe_env(arch)) + # Use environment from boost recipe, cause we use b2 tool from boost + env = self.get_recipe('boost', self.ctx).get_recipe_env(arch) if 'openssl' in recipe.ctx.recipe_build_order: r = self.get_recipe('openssl', self.ctx) env['OPENSSL_BUILD_PATH'] = r.get_build_dir(arch.arch) + env['OPENSSL_INCLUDE'] = join(r.get_build_dir(arch.arch), 'include') env['OPENSSL_VERSION'] = r.version return env + recipe = LibtorrentRecipe() diff --git a/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch b/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch index ec3985af..183705c8 100644 --- a/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch +++ b/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch @@ -1,20 +1,20 @@ ---- libtorrent/bindings/python/setup.py 2016-02-28 08:28:49.000000000 +0100 -+++ patch/bindings/python/setup.py 2016-07-12 12:03:05.256455888 +0200 -@@ -97,7 +97,7 @@ - source_list = os.listdir(os.path.join(os.path.dirname(__file__), "src")) - source_list = [os.path.join("src", s) for s in source_list if s.endswith(".cpp")] - -- ext = [Extension('libtorrent', -+ ext = [Extension('libtorrent_rasterbar', - sources = source_list, - language='c++', - include_dirs = parse_cmd(extra_cmd, '-I'), -@@ -107,7 +107,7 @@ - + target_specific(), - libraries = ['torrent-rasterbar'] + parse_cmd(extra_cmd, '-l'))] - --setup(name = 'python-libtorrent', -+setup(name = 'libtorrent', - version = '1.0.9', - author = 'Arvid Norberg', - author_email = 'arvid@libtorrent.org', +--- libtorrent/bindings/python/setup.py.orig 2018-11-26 22:21:48.772142135 +0100 ++++ libtorrent/bindings/python/setup.py 2018-11-26 22:23:23.092141235 +0100 +@@ -167,7 +167,7 @@ + extra_compile = flags.parse(extra_cmd) + + ext = [Extension( +- 'libtorrent', ++ 'libtorrent_rasterbar', + sources=sorted(source_list), + language='c++', + include_dirs=flags.include_dirs, +@@ -178,7 +178,7 @@ + ] + + setup( +- name='python-libtorrent', ++ name='libtorrent', + version='1.2.0', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', diff --git a/p4a/pythonforandroid/recipes/libtorrent/use-soname-python.patch b/p4a/pythonforandroid/recipes/libtorrent/use-soname-python.patch index f78553d2..14562207 100644 --- a/p4a/pythonforandroid/recipes/libtorrent/use-soname-python.patch +++ b/p4a/pythonforandroid/recipes/libtorrent/use-soname-python.patch @@ -1,11 +1,11 @@ ---- libtorrent/bindings/python/Jamfile 2016-01-17 23:52:45.000000000 +0100 -+++ libtorrent-patch/bindings/python/Jamfile 2016-02-09 17:11:44.261578000 +0100 -@@ -35,7 +35,7 @@ - - if ( gcc in $(properties) ) - { -- result += -Wl,-Bsymbolic ; -+ result += -Wl,-soname=libtorrent.so,-Bsymbolic ; - } - } +--- libtorrent/bindings/python/Jamfile.orig 2018-12-07 16:46:50.851838981 +0100 ++++ libtorrent/bindings/python/Jamfile 2018-12-07 16:49:09.099837663 +0100 +@@ -113,7 +113,7 @@ + + if ( gcc in $(properties) ) + { +- result += -Wl,-Bsymbolic ; ++ result += -Wl,-soname=libtorrent.so,-Bsymbolic ; + } + } diff --git a/p4a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch b/p4a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch index eea9b3f6..6a54071f 100644 --- a/p4a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch +++ b/p4a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch @@ -1,25 +1,21 @@ ---- boost/user-config.jam 2016-03-02 14:31:41.280414820 +0100 -+++ boost-patch/user-config.jam 2016-03-02 14:32:08.904384741 +0100 -@@ -6,6 +6,7 @@ - local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ; - local ARCH = [ os.environ ARCH ] ; - local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ; +--- boost/user-config.jam.orig 2018-12-07 14:16:45.911924859 +0100 ++++ boost/user-config.jam 2018-12-07 14:20:16.243922853 +0100 +@@ -9,6 +9,8 @@ + local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ; + local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ; + local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ; +local OPENSSL_BUILD_PATH = [ os.environ OPENSSL_BUILD_PATH ] ; ++local OPENSSL_VERSION = [ os.environ OPENSSL_VERSION ] ; - using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ : - $(ARCH) -@@ -20,9 +21,14 @@ - -I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include - -I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include - -I$(PYTHON_ROOT)/include/python2.7 -+-I$(OPENSSL_BUILD_PATH)/include -+-I$(OPENSSL_BUILD_PATH)/include/openssl - --sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH) - -L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH) - -L$(PYTHON_ROOT)/lib + #using clang : $(ARCH) : $(ANDROID_BINARIES_PATH)/clang++ : + #$(ANDROID_BINARIES_PATH)/llvm-ar +@@ -56,6 +58,9 @@ + -Wl,-z,relro + -Wl,-z,now + -lc++_shared +-L$(OPENSSL_BUILD_PATH) - -lgnustl_shared - -lpython2.7 +-lcrypto$(OPENSSL_VERSION) +-lssl$(OPENSSL_VERSION) - ; + -L$(PYTHON_ROOT) + -lpython$(PYTHON_LINK_VERSION) + -Wl,-O1 diff --git a/p4a/pythonforandroid/recipes/libtribler/__init__.py b/p4a/pythonforandroid/recipes/libtribler/__init__.py index 856aea3a..134ed9e3 100644 --- a/p4a/pythonforandroid/recipes/libtribler/__init__.py +++ b/p4a/pythonforandroid/recipes/libtribler/__init__.py @@ -1,10 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe """ Privacy with BitTorrent and resilient to shut down http://www.tribler.org """ + + class LibTriblerRecipe(PythonRecipe): version = 'devel' @@ -12,9 +14,11 @@ class LibTriblerRecipe(PythonRecipe): url = 'git+https://github.com/Tribler/tribler.git' depends = ['apsw', 'cryptography', 'ffmpeg', 'libsodium', 'libtorrent', 'm2crypto', - 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'python2', 'twisted', + 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'twisted', ] + conflicts = ['python3'] + python_depends = ['chardet', 'cherrypy', 'configobj', 'decorator', 'feedparser', 'libnacl', 'pyasn1', 'requests', 'six', ] @@ -22,4 +26,4 @@ class LibTriblerRecipe(PythonRecipe): site_packages_name = 'Tribler' -recipe = LibTriblerRecipe() \ No newline at end of file +recipe = LibTriblerRecipe() diff --git a/p4a/pythonforandroid/recipes/libvorbis/__init__.py b/p4a/pythonforandroid/recipes/libvorbis/__init__.py new file mode 100644 index 00000000..87c7a449 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libvorbis/__init__.py @@ -0,0 +1,37 @@ +from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.toolchain import current_directory, shprint +from os.path import join +import sh + + +class VorbisRecipe(NDKRecipe): + version = '1.3.6' + url = 'http://downloads.xiph.org/releases/vorbis/libvorbis-{version}.tar.gz' + opt_depends = ['libogg'] + + generated_libraries = ['libvorbis.so', 'libvorbisfile.so', 'libvorbisenc.so'] + + def get_recipe_env(self, arch=None): + env = super(VorbisRecipe, self).get_recipe_env(arch) + ogg = self.get_recipe('libogg', self.ctx) + env['CFLAGS'] += ' -I{}'.format(join(ogg.get_build_dir(arch.arch), 'include')) + return env + + def build_arch(self, arch): + with current_directory(self.get_build_dir(arch.arch)): + env = self.get_recipe_env(arch) + flags = [ + '--with-sysroot=' + self.ctx.ndk_platform, + '--host=' + arch.toolchain_prefix, + ] + configure = sh.Command('./configure') + shprint(configure, *flags, _env=env) + shprint(sh.make, _env=env) + self.install_libs( + arch, + join('lib', '.libs', 'libvorbis.so'), + join('lib', '.libs', 'libvorbisfile.so'), + join('lib', '.libs', 'libvorbisenc.so')) + + +recipe = VorbisRecipe() diff --git a/p4a/pythonforandroid/recipes/libx264/__init__.py b/p4a/pythonforandroid/recipes/libx264/__init__.py index 0ec3f2a1..c139b4ce 100644 --- a/p4a/pythonforandroid/recipes/libx264/__init__.py +++ b/p4a/pythonforandroid/recipes/libx264/__init__.py @@ -1,14 +1,11 @@ -from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM +from pythonforandroid.toolchain import Recipe, current_directory, shprint from os.path import exists, join, realpath -from os import uname -import glob import sh class LibX264Recipe(Recipe): - version = 'x264-snapshot-20170608-2245-stable' # using mirror url since can't use ftp + version = 'x264-snapshot-20171218-2245-stable' # using mirror url since can't use ftp url = 'http://mirror.yandex.ru/mirrors/ftp.videolan.org/x264/snapshots/{version}.tar.bz2' - md5sum = 'adf3b87f759b5cc9f100f8cf99276f77' def should_build(self, arch): build_dir = self.get_build_dir(arch.arch) @@ -31,4 +28,5 @@ class LibX264Recipe(Recipe): shprint(sh.make, '-j4', _env=env) shprint(sh.make, 'install', _env=env) + recipe = LibX264Recipe() diff --git a/p4a/pythonforandroid/recipes/libxml2/__init__.py b/p4a/pythonforandroid/recipes/libxml2/__init__.py new file mode 100644 index 00000000..cdeaf88d --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxml2/__init__.py @@ -0,0 +1,60 @@ +from pythonforandroid.recipe import Recipe +from pythonforandroid.toolchain import shprint, shutil, current_directory +from os.path import exists, join +import sh + + +class Libxml2Recipe(Recipe): + version = '2.9.8' + url = 'http://xmlsoft.org/sources/libxml2-{version}.tar.gz' + depends = [] + patches = ['add-glob.c.patch'] + + def should_build(self, arch): + super(Libxml2Recipe, self).should_build(arch) + return not exists( + join(self.get_build_dir(arch.arch), '.libs', 'libxml2.a')) + + def build_arch(self, arch): + super(Libxml2Recipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + with current_directory(self.get_build_dir(arch.arch)): + + if not exists('configure'): + shprint(sh.Command('./autogen.sh'), _env=env) + shprint(sh.Command('autoreconf'), '-vif', _env=env) + build_arch = shprint( + sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0] + shprint(sh.Command('./configure'), + '--build=' + build_arch, + '--host=' + arch.command_prefix, + '--target=' + arch.command_prefix, + '--without-modules', + '--without-legacy', + '--without-history', + '--without-debug', + '--without-docbook', + '--without-python', + '--without-threads', + '--without-iconv', + '--without-lzma', + '--disable-shared', + '--enable-static', + _env=env) + + # Ensure we only build libxml2.la as if we do everything + # we'll need the glob dependency which is a big headache + shprint(sh.make, "libxml2.la", _env=env) + + shutil.copyfile('.libs/libxml2.a', + join(self.ctx.libs_dir, 'libxml2.a')) + + def get_recipe_env(self, arch): + env = super(Libxml2Recipe, self).get_recipe_env(arch) + env['CONFIG_SHELL'] = '/bin/bash' + env['SHELL'] = '/bin/bash' + env['CC'] += ' -I' + self.get_build_dir(arch.arch) + return env + + +recipe = Libxml2Recipe() diff --git a/p4a/pythonforandroid/recipes/libxml2/add-glob.c.patch b/p4a/pythonforandroid/recipes/libxml2/add-glob.c.patch new file mode 100644 index 00000000..776c0c4d --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxml2/add-glob.c.patch @@ -0,0 +1,1038 @@ +From c97da18834aa41637e3e550bccb70bd2dd0ca3b9 Mon Sep 17 00:00:00 2001 +From: Zachary Goldberg +Date: Wed, 20 Apr 2016 21:21:52 -0700 +Subject: [PATCH] Add glob + +--- + glob.c | 906 ++++++++++++++++++++++++++++++++ + glob.h | 105 ++++ + 2 files changed, 1011 insertions(+) + create mode 100644 glob.c + create mode 100644 glob.h + +diff --git a/glob.c b/glob.c +new file mode 100644 +index 0000000..cec80ed +--- /dev/null ++++ b/glob.c +@@ -0,0 +1,906 @@ ++/* ++ * Natanael Arndt, 2011: removed collate.h dependencies ++ * (my changes are trivial) ++ * ++ * Copyright (c) 1989, 1993 ++ * The Regents of the University of California. All rights reserved. ++ * ++ * This code is derived from software contributed to Berkeley by ++ * Guido van Rossum. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 4. Neither the name of the University nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#if defined(LIBC_SCCS) && !defined(lint) ++static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; ++#endif /* LIBC_SCCS and not lint */ ++#include ++__FBSDID("$FreeBSD$"); ++ ++/* ++ * glob(3) -- a superset of the one defined in POSIX 1003.2. ++ * ++ * The [!...] convention to negate a range is supported (SysV, Posix, ksh). ++ * ++ * Optional extra services, controlled by flags not defined by POSIX: ++ * ++ * GLOB_QUOTE: ++ * Escaping convention: \ inhibits any special meaning the following ++ * character might have (except \ at end of string is retained). ++ * GLOB_MAGCHAR: ++ * Set in gl_flags if pattern contained a globbing character. ++ * GLOB_NOMAGIC: ++ * Same as GLOB_NOCHECK, but it will only append pattern if it did ++ * not contain any magic characters. [Used in csh style globbing] ++ * GLOB_ALTDIRFUNC: ++ * Use alternately specified directory access functions. ++ * GLOB_TILDE: ++ * expand ~user/foo to the /home/dir/of/user/foo ++ * GLOB_BRACE: ++ * expand {1,2}{a,b} to 1a 1b 2a 2b ++ * gl_matchc: ++ * Number of matches in the current invocation of glob. ++ */ ++ ++/* ++ * Some notes on multibyte character support: ++ * 1. Patterns with illegal byte sequences match nothing - even if ++ * GLOB_NOCHECK is specified. ++ * 2. Illegal byte sequences in filenames are handled by treating them as ++ * single-byte characters with a value of the first byte of the sequence ++ * cast to wchar_t. ++ * 3. State-dependent encodings are not currently supported. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DOLLAR '$' ++#define DOT '.' ++#define EOS '\0' ++#define LBRACKET '[' ++#define NOT '!' ++#define QUESTION '?' ++#define QUOTE '\\' ++#define RANGE '-' ++#define RBRACKET ']' ++#define SEP '/' ++#define STAR '*' ++#define TILDE '~' ++#define UNDERSCORE '_' ++#define LBRACE '{' ++#define RBRACE '}' ++#define SLASH '/' ++#define COMMA ',' ++ ++#ifndef DEBUG ++ ++#define M_QUOTE 0x8000000000ULL ++#define M_PROTECT 0x4000000000ULL ++#define M_MASK 0xffffffffffULL ++#define M_CHAR 0x00ffffffffULL ++ ++typedef uint_fast64_t Char; ++ ++#else ++ ++#define M_QUOTE 0x80 ++#define M_PROTECT 0x40 ++#define M_MASK 0xff ++#define M_CHAR 0x7f ++ ++typedef char Char; ++ ++#endif ++ ++ ++#define CHAR(c) ((Char)((c)&M_CHAR)) ++#define META(c) ((Char)((c)|M_QUOTE)) ++#define M_ALL META('*') ++#define M_END META(']') ++#define M_NOT META('!') ++#define M_ONE META('?') ++#define M_RNG META('-') ++#define M_SET META('[') ++#define ismeta(c) (((c)&M_QUOTE) != 0) ++ ++ ++static int compare(const void *, const void *); ++static int g_Ctoc(const Char *, char *, size_t); ++static int g_lstat(Char *, struct stat *, glob_t *); ++static DIR *g_opendir(Char *, glob_t *); ++static const Char *g_strchr(const Char *, wchar_t); ++#ifdef notdef ++static Char *g_strcat(Char *, const Char *); ++#endif ++static int g_stat(Char *, struct stat *, glob_t *); ++static int glob0(const Char *, glob_t *, size_t *); ++static int glob1(Char *, glob_t *, size_t *); ++static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *); ++static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *); ++static int globextend(const Char *, glob_t *, size_t *); ++static const Char * ++ globtilde(const Char *, Char *, size_t, glob_t *); ++static int globexp1(const Char *, glob_t *, size_t *); ++static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *); ++static int match(Char *, Char *, Char *); ++#ifdef DEBUG ++static void qprintf(const char *, Char *); ++#endif ++ ++int ++glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) ++{ ++ const char *patnext; ++ size_t limit; ++ Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; ++ mbstate_t mbs; ++ wchar_t wc; ++ size_t clen; ++ ++ patnext = pattern; ++ if (!(flags & GLOB_APPEND)) { ++ pglob->gl_pathc = 0; ++ pglob->gl_pathv = NULL; ++ if (!(flags & GLOB_DOOFFS)) ++ pglob->gl_offs = 0; ++ } ++ if (flags & GLOB_LIMIT) { ++ limit = pglob->gl_matchc; ++ if (limit == 0) ++ limit = ARG_MAX; ++ } else ++ limit = 0; ++ pglob->gl_flags = flags & ~GLOB_MAGCHAR; ++ pglob->gl_errfunc = errfunc; ++ pglob->gl_matchc = 0; ++ ++ bufnext = patbuf; ++ bufend = bufnext + MAXPATHLEN - 1; ++ if (flags & GLOB_NOESCAPE) { ++ memset(&mbs, 0, sizeof(mbs)); ++ while (bufend - bufnext >= MB_CUR_MAX) { ++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) ++ return (GLOB_NOMATCH); ++ else if (clen == 0) ++ break; ++ *bufnext++ = wc; ++ patnext += clen; ++ } ++ } else { ++ /* Protect the quoted characters. */ ++ memset(&mbs, 0, sizeof(mbs)); ++ while (bufend - bufnext >= MB_CUR_MAX) { ++ if (*patnext == QUOTE) { ++ if (*++patnext == EOS) { ++ *bufnext++ = QUOTE | M_PROTECT; ++ continue; ++ } ++ prot = M_PROTECT; ++ } else ++ prot = 0; ++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) ++ return (GLOB_NOMATCH); ++ else if (clen == 0) ++ break; ++ *bufnext++ = wc | prot; ++ patnext += clen; ++ } ++ } ++ *bufnext = EOS; ++ ++ if (flags & GLOB_BRACE) ++ return globexp1(patbuf, pglob, &limit); ++ else ++ return glob0(patbuf, pglob, &limit); ++} ++ ++/* ++ * Expand recursively a glob {} pattern. When there is no more expansion ++ * invoke the standard globbing routine to glob the rest of the magic ++ * characters ++ */ ++static int ++globexp1(const Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ const Char* ptr = pattern; ++ int rv; ++ ++ /* Protect a single {}, for find(1), like csh */ ++ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) ++ return glob0(pattern, pglob, limit); ++ ++ while ((ptr = g_strchr(ptr, LBRACE)) != NULL) ++ if (!globexp2(ptr, pattern, pglob, &rv, limit)) ++ return rv; ++ ++ return glob0(pattern, pglob, limit); ++} ++ ++ ++/* ++ * Recursive brace globbing helper. Tries to expand a single brace. ++ * If it succeeds then it invokes globexp1 with the new pattern. ++ * If it fails then it tries to glob the rest of the pattern and returns. ++ */ ++static int ++globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit) ++{ ++ int i; ++ Char *lm, *ls; ++ const Char *pe, *pm, *pm1, *pl; ++ Char patbuf[MAXPATHLEN]; ++ ++ /* copy part up to the brace */ ++ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) ++ continue; ++ *lm = EOS; ++ ls = lm; ++ ++ /* Find the balanced brace */ ++ for (i = 0, pe = ++ptr; *pe; pe++) ++ if (*pe == LBRACKET) { ++ /* Ignore everything between [] */ ++ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) ++ continue; ++ if (*pe == EOS) { ++ /* ++ * We could not find a matching RBRACKET. ++ * Ignore and just look for RBRACE ++ */ ++ pe = pm; ++ } ++ } ++ else if (*pe == LBRACE) ++ i++; ++ else if (*pe == RBRACE) { ++ if (i == 0) ++ break; ++ i--; ++ } ++ ++ /* Non matching braces; just glob the pattern */ ++ if (i != 0 || *pe == EOS) { ++ *rv = glob0(patbuf, pglob, limit); ++ return 0; ++ } ++ ++ for (i = 0, pl = pm = ptr; pm <= pe; pm++) ++ switch (*pm) { ++ case LBRACKET: ++ /* Ignore everything between [] */ ++ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) ++ continue; ++ if (*pm == EOS) { ++ /* ++ * We could not find a matching RBRACKET. ++ * Ignore and just look for RBRACE ++ */ ++ pm = pm1; ++ } ++ break; ++ ++ case LBRACE: ++ i++; ++ break; ++ ++ case RBRACE: ++ if (i) { ++ i--; ++ break; ++ } ++ /* FALLTHROUGH */ ++ case COMMA: ++ if (i && *pm == COMMA) ++ break; ++ else { ++ /* Append the current string */ ++ for (lm = ls; (pl < pm); *lm++ = *pl++) ++ continue; ++ /* ++ * Append the rest of the pattern after the ++ * closing brace ++ */ ++ for (pl = pe + 1; (*lm++ = *pl++) != EOS;) ++ continue; ++ ++ /* Expand the current pattern */ ++#ifdef DEBUG ++ qprintf("globexp2:", patbuf); ++#endif ++ *rv = globexp1(patbuf, pglob, limit); ++ ++ /* move after the comma, to the next string */ ++ pl = pm + 1; ++ } ++ break; ++ ++ default: ++ break; ++ } ++ *rv = 0; ++ return 0; ++} ++ ++ ++ ++/* ++ * expand tilde from the passwd file. ++ */ ++static const Char * ++globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) ++{ ++ struct passwd *pwd; ++ char *h; ++ const Char *p; ++ Char *b, *eb; ++ ++ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) ++ return pattern; ++ ++ /* ++ * Copy up to the end of the string or / ++ */ ++ eb = &patbuf[patbuf_len - 1]; ++ for (p = pattern + 1, h = (char *) patbuf; ++ h < (char *)eb && *p && *p != SLASH; *h++ = *p++) ++ continue; ++ ++ *h = EOS; ++ ++ if (((char *) patbuf)[0] == EOS) { ++ /* ++ * handle a plain ~ or ~/ by expanding $HOME first (iff ++ * we're not running setuid or setgid) and then trying ++ * the password file ++ */ ++ if (issetugid() != 0 || ++ (h = getenv("HOME")) == NULL) { ++ if (((h = getlogin()) != NULL && ++ (pwd = getpwnam(h)) != NULL) || ++ (pwd = getpwuid(getuid())) != NULL) ++ h = pwd->pw_dir; ++ else ++ return pattern; ++ } ++ } ++ else { ++ /* ++ * Expand a ~user ++ */ ++ if ((pwd = getpwnam((char*) patbuf)) == NULL) ++ return pattern; ++ else ++ h = pwd->pw_dir; ++ } ++ ++ /* Copy the home directory */ ++ for (b = patbuf; b < eb && *h; *b++ = *h++) ++ continue; ++ ++ /* Append the rest of the pattern */ ++ while (b < eb && (*b++ = *p++) != EOS) ++ continue; ++ *b = EOS; ++ ++ return patbuf; ++} ++ ++ ++/* ++ * The main glob() routine: compiles the pattern (optionally processing ++ * quotes), calls glob1() to do the real pattern matching, and finally ++ * sorts the list (unless unsorted operation is requested). Returns 0 ++ * if things went well, nonzero if errors occurred. ++ */ ++static int ++glob0(const Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ const Char *qpatnext; ++ int err; ++ size_t oldpathc; ++ Char *bufnext, c, patbuf[MAXPATHLEN]; ++ ++ qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); ++ oldpathc = pglob->gl_pathc; ++ bufnext = patbuf; ++ ++ /* We don't need to check for buffer overflow any more. */ ++ while ((c = *qpatnext++) != EOS) { ++ switch (c) { ++ case LBRACKET: ++ c = *qpatnext; ++ if (c == NOT) ++ ++qpatnext; ++ if (*qpatnext == EOS || ++ g_strchr(qpatnext+1, RBRACKET) == NULL) { ++ *bufnext++ = LBRACKET; ++ if (c == NOT) ++ --qpatnext; ++ break; ++ } ++ *bufnext++ = M_SET; ++ if (c == NOT) ++ *bufnext++ = M_NOT; ++ c = *qpatnext++; ++ do { ++ *bufnext++ = CHAR(c); ++ if (*qpatnext == RANGE && ++ (c = qpatnext[1]) != RBRACKET) { ++ *bufnext++ = M_RNG; ++ *bufnext++ = CHAR(c); ++ qpatnext += 2; ++ } ++ } while ((c = *qpatnext++) != RBRACKET); ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ *bufnext++ = M_END; ++ break; ++ case QUESTION: ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ *bufnext++ = M_ONE; ++ break; ++ case STAR: ++ pglob->gl_flags |= GLOB_MAGCHAR; ++ /* collapse adjacent stars to one, ++ * to avoid exponential behavior ++ */ ++ if (bufnext == patbuf || bufnext[-1] != M_ALL) ++ *bufnext++ = M_ALL; ++ break; ++ default: ++ *bufnext++ = CHAR(c); ++ break; ++ } ++ } ++ *bufnext = EOS; ++#ifdef DEBUG ++ qprintf("glob0:", patbuf); ++#endif ++ ++ if ((err = glob1(patbuf, pglob, limit)) != 0) ++ return(err); ++ ++ /* ++ * If there was no match we are going to append the pattern ++ * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified ++ * and the pattern did not contain any magic characters ++ * GLOB_NOMAGIC is there just for compatibility with csh. ++ */ ++ if (pglob->gl_pathc == oldpathc) { ++ if (((pglob->gl_flags & GLOB_NOCHECK) || ++ ((pglob->gl_flags & GLOB_NOMAGIC) && ++ !(pglob->gl_flags & GLOB_MAGCHAR)))) ++ return(globextend(pattern, pglob, limit)); ++ else ++ return(GLOB_NOMATCH); ++ } ++ if (!(pglob->gl_flags & GLOB_NOSORT)) ++ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc, ++ pglob->gl_pathc - oldpathc, sizeof(char *), compare); ++ return(0); ++} ++ ++static int ++compare(const void *p, const void *q) ++{ ++ return(strcmp(*(char **)p, *(char **)q)); ++} ++ ++static int ++glob1(Char *pattern, glob_t *pglob, size_t *limit) ++{ ++ Char pathbuf[MAXPATHLEN]; ++ ++ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ ++ if (*pattern == EOS) ++ return(0); ++ return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, ++ pattern, pglob, limit)); ++} ++ ++/* ++ * The functions glob2 and glob3 are mutually recursive; there is one level ++ * of recursion for each segment in the pattern that contains one or more ++ * meta characters. ++ */ ++static int ++glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, ++ glob_t *pglob, size_t *limit) ++{ ++ struct stat sb; ++ Char *p, *q; ++ int anymeta; ++ ++ /* ++ * Loop over pattern segments until end of pattern or until ++ * segment with meta character found. ++ */ ++ for (anymeta = 0;;) { ++ if (*pattern == EOS) { /* End of pattern? */ ++ *pathend = EOS; ++ if (g_lstat(pathbuf, &sb, pglob)) ++ return(0); ++ ++ if (((pglob->gl_flags & GLOB_MARK) && ++ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ++ || (S_ISLNK(sb.st_mode) && ++ (g_stat(pathbuf, &sb, pglob) == 0) && ++ S_ISDIR(sb.st_mode)))) { ++ if (pathend + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend++ = SEP; ++ *pathend = EOS; ++ } ++ ++pglob->gl_matchc; ++ return(globextend(pathbuf, pglob, limit)); ++ } ++ ++ /* Find end of next segment, copy tentatively to pathend. */ ++ q = pathend; ++ p = pattern; ++ while (*p != EOS && *p != SEP) { ++ if (ismeta(*p)) ++ anymeta = 1; ++ if (q + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *q++ = *p++; ++ } ++ ++ if (!anymeta) { /* No expansion, do next segment. */ ++ pathend = q; ++ pattern = p; ++ while (*pattern == SEP) { ++ if (pathend + 1 > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend++ = *pattern++; ++ } ++ } else /* Need expansion, recurse. */ ++ return(glob3(pathbuf, pathend, pathend_last, pattern, p, ++ pglob, limit)); ++ } ++ /* NOTREACHED */ ++} ++ ++static int ++glob3(Char *pathbuf, Char *pathend, Char *pathend_last, ++ Char *pattern, Char *restpattern, ++ glob_t *pglob, size_t *limit) ++{ ++ struct dirent *dp; ++ DIR *dirp; ++ int err; ++ char buf[MAXPATHLEN]; ++ ++ /* ++ * The readdirfunc declaration can't be prototyped, because it is ++ * assigned, below, to two functions which are prototyped in glob.h ++ * and dirent.h as taking pointers to differently typed opaque ++ * structures. ++ */ ++ struct dirent *(*readdirfunc)(); ++ ++ if (pathend > pathend_last) ++ return (GLOB_ABORTED); ++ *pathend = EOS; ++ errno = 0; ++ ++ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { ++ /* TODO: don't call for ENOENT or ENOTDIR? */ ++ if (pglob->gl_errfunc) { ++ if (g_Ctoc(pathbuf, buf, sizeof(buf))) ++ return (GLOB_ABORTED); ++ if (pglob->gl_errfunc(buf, errno) || ++ pglob->gl_flags & GLOB_ERR) ++ return (GLOB_ABORTED); ++ } ++ return(0); ++ } ++ ++ err = 0; ++ ++ /* Search directory for matching names. */ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ readdirfunc = pglob->gl_readdir; ++ else ++ readdirfunc = readdir; ++ while ((dp = (*readdirfunc)(dirp))) { ++ char *sc; ++ Char *dc; ++ wchar_t wc; ++ size_t clen; ++ mbstate_t mbs; ++ ++ /* Initial DOT must be matched literally. */ ++ if (dp->d_name[0] == DOT && *pattern != DOT) ++ continue; ++ memset(&mbs, 0, sizeof(mbs)); ++ dc = pathend; ++ sc = dp->d_name; ++ while (dc < pathend_last) { ++ clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); ++ if (clen == (size_t)-1 || clen == (size_t)-2) { ++ wc = *sc; ++ clen = 1; ++ memset(&mbs, 0, sizeof(mbs)); ++ } ++ if ((*dc++ = wc) == EOS) ++ break; ++ sc += clen; ++ } ++ if (!match(pathend, pattern, restpattern)) { ++ *pathend = EOS; ++ continue; ++ } ++ err = glob2(pathbuf, --dc, pathend_last, restpattern, ++ pglob, limit); ++ if (err) ++ break; ++ } ++ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ (*pglob->gl_closedir)(dirp); ++ else ++ closedir(dirp); ++ return(err); ++} ++ ++ ++/* ++ * Extend the gl_pathv member of a glob_t structure to accomodate a new item, ++ * add the new item, and update gl_pathc. ++ * ++ * This assumes the BSD realloc, which only copies the block when its size ++ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic ++ * behavior. ++ * ++ * Return 0 if new item added, error code if memory couldn't be allocated. ++ * ++ * Invariant of the glob_t structure: ++ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and ++ * gl_pathv points to (gl_offs + gl_pathc + 1) items. ++ */ ++static int ++globextend(const Char *path, glob_t *pglob, size_t *limit) ++{ ++ char **pathv; ++ size_t i, newsize, len; ++ char *copy; ++ const Char *p; ++ ++ if (*limit && pglob->gl_pathc > *limit) { ++ errno = 0; ++ return (GLOB_NOSPACE); ++ } ++ ++ newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); ++ pathv = pglob->gl_pathv ? ++ realloc((char *)pglob->gl_pathv, newsize) : ++ malloc(newsize); ++ if (pathv == NULL) { ++ if (pglob->gl_pathv) { ++ free(pglob->gl_pathv); ++ pglob->gl_pathv = NULL; ++ } ++ return(GLOB_NOSPACE); ++ } ++ ++ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { ++ /* first time around -- clear initial gl_offs items */ ++ pathv += pglob->gl_offs; ++ for (i = pglob->gl_offs + 1; --i > 0; ) ++ *--pathv = NULL; ++ } ++ pglob->gl_pathv = pathv; ++ ++ for (p = path; *p++;) ++ continue; ++ len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ ++ if ((copy = malloc(len)) != NULL) { ++ if (g_Ctoc(path, copy, len)) { ++ free(copy); ++ return (GLOB_NOSPACE); ++ } ++ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; ++ } ++ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; ++ return(copy == NULL ? GLOB_NOSPACE : 0); ++} ++ ++/* ++ * pattern matching function for filenames. Each occurrence of the * ++ * pattern causes a recursion level. ++ */ ++static int ++match(Char *name, Char *pat, Char *patend) ++{ ++ int ok, negate_range; ++ Char c, k; ++ ++ while (pat < patend) { ++ c = *pat++; ++ switch (c & M_MASK) { ++ case M_ALL: ++ if (pat == patend) ++ return(1); ++ do ++ if (match(name, pat, patend)) ++ return(1); ++ while (*name++ != EOS); ++ return(0); ++ case M_ONE: ++ if (*name++ == EOS) ++ return(0); ++ break; ++ case M_SET: ++ ok = 0; ++ if ((k = *name++) == EOS) ++ return(0); ++ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) ++ ++pat; ++ while (((c = *pat++) & M_MASK) != M_END) ++ if ((*pat & M_MASK) == M_RNG) { ++ if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1; ++ pat += 2; ++ } else if (c == k) ++ ok = 1; ++ if (ok == negate_range) ++ return(0); ++ break; ++ default: ++ if (*name++ != c) ++ return(0); ++ break; ++ } ++ } ++ return(*name == EOS); ++} ++ ++/* Free allocated data belonging to a glob_t structure. */ ++void ++globfree(glob_t *pglob) ++{ ++ size_t i; ++ char **pp; ++ ++ if (pglob->gl_pathv != NULL) { ++ pp = pglob->gl_pathv + pglob->gl_offs; ++ for (i = pglob->gl_pathc; i--; ++pp) ++ if (*pp) ++ free(*pp); ++ free(pglob->gl_pathv); ++ pglob->gl_pathv = NULL; ++ } ++} ++ ++static DIR * ++g_opendir(Char *str, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (!*str) ++ strcpy(buf, "."); ++ else { ++ if (g_Ctoc(str, buf, sizeof(buf))) ++ return (NULL); ++ } ++ ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_opendir)(buf)); ++ ++ return(opendir(buf)); ++} ++ ++static int ++g_lstat(Char *fn, struct stat *sb, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (g_Ctoc(fn, buf, sizeof(buf))) { ++ errno = ENAMETOOLONG; ++ return (-1); ++ } ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_lstat)(buf, sb)); ++ return(lstat(buf, sb)); ++} ++ ++static int ++g_stat(Char *fn, struct stat *sb, glob_t *pglob) ++{ ++ char buf[MAXPATHLEN]; ++ ++ if (g_Ctoc(fn, buf, sizeof(buf))) { ++ errno = ENAMETOOLONG; ++ return (-1); ++ } ++ if (pglob->gl_flags & GLOB_ALTDIRFUNC) ++ return((*pglob->gl_stat)(buf, sb)); ++ return(stat(buf, sb)); ++} ++ ++static const Char * ++g_strchr(const Char *str, wchar_t ch) ++{ ++ ++ do { ++ if (*str == ch) ++ return (str); ++ } while (*str++); ++ return (NULL); ++} ++ ++static int ++g_Ctoc(const Char *str, char *buf, size_t len) ++{ ++ mbstate_t mbs; ++ size_t clen; ++ ++ memset(&mbs, 0, sizeof(mbs)); ++ while (len >= MB_CUR_MAX) { ++ clen = wcrtomb(buf, *str, &mbs); ++ if (clen == (size_t)-1) ++ return (1); ++ if (*str == L'\0') ++ return (0); ++ str++; ++ buf += clen; ++ len -= clen; ++ } ++ return (1); ++} ++ ++#ifdef DEBUG ++static void ++qprintf(const char *str, Char *s) ++{ ++ Char *p; ++ ++ (void)printf("%s:\n", str); ++ for (p = s; *p; p++) ++ (void)printf("%c", CHAR(*p)); ++ (void)printf("\n"); ++ for (p = s; *p; p++) ++ (void)printf("%c", *p & M_PROTECT ? '"' : ' '); ++ (void)printf("\n"); ++ for (p = s; *p; p++) ++ (void)printf("%c", ismeta(*p) ? '_' : ' '); ++ (void)printf("\n"); ++} ++#endif +diff --git a/glob.h b/glob.h +new file mode 100644 +index 0000000..351b6c4 +--- /dev/null ++++ b/glob.h +@@ -0,0 +1,105 @@ ++/* ++ * Copyright (c) 1989, 1993 ++ * The Regents of the University of California. All rights reserved. ++ * ++ * This code is derived from software contributed to Berkeley by ++ * Guido van Rossum. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the name of the University nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ * ++ * @(#)glob.h 8.1 (Berkeley) 6/2/93 ++ * $FreeBSD$ ++ */ ++ ++#ifndef _GLOB_H_ ++#define _GLOB_H_ ++ ++#include ++#include ++ ++#ifndef _SIZE_T_DECLARED ++typedef __size_t size_t; ++#define _SIZE_T_DECLARED ++#endif ++ ++struct stat; ++typedef struct { ++ size_t gl_pathc; /* Count of total paths so far. */ ++ size_t gl_matchc; /* Count of paths matching pattern. */ ++ size_t gl_offs; /* Reserved at beginning of gl_pathv. */ ++ int gl_flags; /* Copy of flags parameter to glob. */ ++ char **gl_pathv; /* List of paths matching pattern. */ ++ /* Copy of errfunc parameter to glob. */ ++ int (*gl_errfunc)(const char *, int); ++ ++ /* ++ * Alternate filesystem access methods for glob; replacement ++ * versions of closedir(3), readdir(3), opendir(3), stat(2) ++ * and lstat(2). ++ */ ++ void (*gl_closedir)(void *); ++ struct dirent *(*gl_readdir)(void *); ++ void *(*gl_opendir)(const char *); ++ int (*gl_lstat)(const char *, struct stat *); ++ int (*gl_stat)(const char *, struct stat *); ++} glob_t; ++ ++#if __POSIX_VISIBLE >= 199209 ++/* Believed to have been introduced in 1003.2-1992 */ ++#define GLOB_APPEND 0x0001 /* Append to output from previous call. */ ++#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ ++#define GLOB_ERR 0x0004 /* Return on error. */ ++#define GLOB_MARK 0x0008 /* Append / to matching directories. */ ++#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ ++#define GLOB_NOSORT 0x0020 /* Don't sort. */ ++#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ ++ ++/* Error values returned by glob(3) */ ++#define GLOB_NOSPACE (-1) /* Malloc call failed. */ ++#define GLOB_ABORTED (-2) /* Unignored error. */ ++#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ ++#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */ ++#endif /* __POSIX_VISIBLE >= 199209 */ ++ ++#if __BSD_VISIBLE ++#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ ++#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ ++#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ ++#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ ++#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ ++#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ ++#define GLOB_LIMIT 0x1000 /* limit number of returned paths */ ++ ++/* source compatibility, these are the old names */ ++#define GLOB_MAXPATH GLOB_LIMIT ++#define GLOB_ABEND GLOB_ABORTED ++#endif /* __BSD_VISIBLE */ ++ ++__BEGIN_DECLS ++int glob(const char *, int, int (*)(const char *, int), glob_t *); ++void globfree(glob_t *); ++__END_DECLS ++ ++#endif /* !_GLOB_H_ */ +-- +1.9.1 + diff --git a/p4a/pythonforandroid/recipes/libxml2/glob.c b/p4a/pythonforandroid/recipes/libxml2/glob.c new file mode 100644 index 00000000..cec80ed7 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxml2/glob.c @@ -0,0 +1,906 @@ +/* + * Natanael Arndt, 2011: removed collate.h dependencies + * (my changes are trivial) + * + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; +#endif /* LIBC_SCCS and not lint */ +#include +__FBSDID("$FreeBSD$"); + +/* + * glob(3) -- a superset of the one defined in POSIX 1003.2. + * + * The [!...] convention to negate a range is supported (SysV, Posix, ksh). + * + * Optional extra services, controlled by flags not defined by POSIX: + * + * GLOB_QUOTE: + * Escaping convention: \ inhibits any special meaning the following + * character might have (except \ at end of string is retained). + * GLOB_MAGCHAR: + * Set in gl_flags if pattern contained a globbing character. + * GLOB_NOMAGIC: + * Same as GLOB_NOCHECK, but it will only append pattern if it did + * not contain any magic characters. [Used in csh style globbing] + * GLOB_ALTDIRFUNC: + * Use alternately specified directory access functions. + * GLOB_TILDE: + * expand ~user/foo to the /home/dir/of/user/foo + * GLOB_BRACE: + * expand {1,2}{a,b} to 1a 1b 2a 2b + * gl_matchc: + * Number of matches in the current invocation of glob. + */ + +/* + * Some notes on multibyte character support: + * 1. Patterns with illegal byte sequences match nothing - even if + * GLOB_NOCHECK is specified. + * 2. Illegal byte sequences in filenames are handled by treating them as + * single-byte characters with a value of the first byte of the sequence + * cast to wchar_t. + * 3. State-dependent encodings are not currently supported. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DOLLAR '$' +#define DOT '.' +#define EOS '\0' +#define LBRACKET '[' +#define NOT '!' +#define QUESTION '?' +#define QUOTE '\\' +#define RANGE '-' +#define RBRACKET ']' +#define SEP '/' +#define STAR '*' +#define TILDE '~' +#define UNDERSCORE '_' +#define LBRACE '{' +#define RBRACE '}' +#define SLASH '/' +#define COMMA ',' + +#ifndef DEBUG + +#define M_QUOTE 0x8000000000ULL +#define M_PROTECT 0x4000000000ULL +#define M_MASK 0xffffffffffULL +#define M_CHAR 0x00ffffffffULL + +typedef uint_fast64_t Char; + +#else + +#define M_QUOTE 0x80 +#define M_PROTECT 0x40 +#define M_MASK 0xff +#define M_CHAR 0x7f + +typedef char Char; + +#endif + + +#define CHAR(c) ((Char)((c)&M_CHAR)) +#define META(c) ((Char)((c)|M_QUOTE)) +#define M_ALL META('*') +#define M_END META(']') +#define M_NOT META('!') +#define M_ONE META('?') +#define M_RNG META('-') +#define M_SET META('[') +#define ismeta(c) (((c)&M_QUOTE) != 0) + + +static int compare(const void *, const void *); +static int g_Ctoc(const Char *, char *, size_t); +static int g_lstat(Char *, struct stat *, glob_t *); +static DIR *g_opendir(Char *, glob_t *); +static const Char *g_strchr(const Char *, wchar_t); +#ifdef notdef +static Char *g_strcat(Char *, const Char *); +#endif +static int g_stat(Char *, struct stat *, glob_t *); +static int glob0(const Char *, glob_t *, size_t *); +static int glob1(Char *, glob_t *, size_t *); +static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *); +static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *); +static int globextend(const Char *, glob_t *, size_t *); +static const Char * + globtilde(const Char *, Char *, size_t, glob_t *); +static int globexp1(const Char *, glob_t *, size_t *); +static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *); +static int match(Char *, Char *, Char *); +#ifdef DEBUG +static void qprintf(const char *, Char *); +#endif + +int +glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) +{ + const char *patnext; + size_t limit; + Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; + mbstate_t mbs; + wchar_t wc; + size_t clen; + + patnext = pattern; + if (!(flags & GLOB_APPEND)) { + pglob->gl_pathc = 0; + pglob->gl_pathv = NULL; + if (!(flags & GLOB_DOOFFS)) + pglob->gl_offs = 0; + } + if (flags & GLOB_LIMIT) { + limit = pglob->gl_matchc; + if (limit == 0) + limit = ARG_MAX; + } else + limit = 0; + pglob->gl_flags = flags & ~GLOB_MAGCHAR; + pglob->gl_errfunc = errfunc; + pglob->gl_matchc = 0; + + bufnext = patbuf; + bufend = bufnext + MAXPATHLEN - 1; + if (flags & GLOB_NOESCAPE) { + memset(&mbs, 0, sizeof(mbs)); + while (bufend - bufnext >= MB_CUR_MAX) { + clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) + return (GLOB_NOMATCH); + else if (clen == 0) + break; + *bufnext++ = wc; + patnext += clen; + } + } else { + /* Protect the quoted characters. */ + memset(&mbs, 0, sizeof(mbs)); + while (bufend - bufnext >= MB_CUR_MAX) { + if (*patnext == QUOTE) { + if (*++patnext == EOS) { + *bufnext++ = QUOTE | M_PROTECT; + continue; + } + prot = M_PROTECT; + } else + prot = 0; + clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) + return (GLOB_NOMATCH); + else if (clen == 0) + break; + *bufnext++ = wc | prot; + patnext += clen; + } + } + *bufnext = EOS; + + if (flags & GLOB_BRACE) + return globexp1(patbuf, pglob, &limit); + else + return glob0(patbuf, pglob, &limit); +} + +/* + * Expand recursively a glob {} pattern. When there is no more expansion + * invoke the standard globbing routine to glob the rest of the magic + * characters + */ +static int +globexp1(const Char *pattern, glob_t *pglob, size_t *limit) +{ + const Char* ptr = pattern; + int rv; + + /* Protect a single {}, for find(1), like csh */ + if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) + return glob0(pattern, pglob, limit); + + while ((ptr = g_strchr(ptr, LBRACE)) != NULL) + if (!globexp2(ptr, pattern, pglob, &rv, limit)) + return rv; + + return glob0(pattern, pglob, limit); +} + + +/* + * Recursive brace globbing helper. Tries to expand a single brace. + * If it succeeds then it invokes globexp1 with the new pattern. + * If it fails then it tries to glob the rest of the pattern and returns. + */ +static int +globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit) +{ + int i; + Char *lm, *ls; + const Char *pe, *pm, *pm1, *pl; + Char patbuf[MAXPATHLEN]; + + /* copy part up to the brace */ + for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) + continue; + *lm = EOS; + ls = lm; + + /* Find the balanced brace */ + for (i = 0, pe = ++ptr; *pe; pe++) + if (*pe == LBRACKET) { + /* Ignore everything between [] */ + for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) + continue; + if (*pe == EOS) { + /* + * We could not find a matching RBRACKET. + * Ignore and just look for RBRACE + */ + pe = pm; + } + } + else if (*pe == LBRACE) + i++; + else if (*pe == RBRACE) { + if (i == 0) + break; + i--; + } + + /* Non matching braces; just glob the pattern */ + if (i != 0 || *pe == EOS) { + *rv = glob0(patbuf, pglob, limit); + return 0; + } + + for (i = 0, pl = pm = ptr; pm <= pe; pm++) + switch (*pm) { + case LBRACKET: + /* Ignore everything between [] */ + for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) + continue; + if (*pm == EOS) { + /* + * We could not find a matching RBRACKET. + * Ignore and just look for RBRACE + */ + pm = pm1; + } + break; + + case LBRACE: + i++; + break; + + case RBRACE: + if (i) { + i--; + break; + } + /* FALLTHROUGH */ + case COMMA: + if (i && *pm == COMMA) + break; + else { + /* Append the current string */ + for (lm = ls; (pl < pm); *lm++ = *pl++) + continue; + /* + * Append the rest of the pattern after the + * closing brace + */ + for (pl = pe + 1; (*lm++ = *pl++) != EOS;) + continue; + + /* Expand the current pattern */ +#ifdef DEBUG + qprintf("globexp2:", patbuf); +#endif + *rv = globexp1(patbuf, pglob, limit); + + /* move after the comma, to the next string */ + pl = pm + 1; + } + break; + + default: + break; + } + *rv = 0; + return 0; +} + + + +/* + * expand tilde from the passwd file. + */ +static const Char * +globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) +{ + struct passwd *pwd; + char *h; + const Char *p; + Char *b, *eb; + + if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) + return pattern; + + /* + * Copy up to the end of the string or / + */ + eb = &patbuf[patbuf_len - 1]; + for (p = pattern + 1, h = (char *) patbuf; + h < (char *)eb && *p && *p != SLASH; *h++ = *p++) + continue; + + *h = EOS; + + if (((char *) patbuf)[0] == EOS) { + /* + * handle a plain ~ or ~/ by expanding $HOME first (iff + * we're not running setuid or setgid) and then trying + * the password file + */ + if (issetugid() != 0 || + (h = getenv("HOME")) == NULL) { + if (((h = getlogin()) != NULL && + (pwd = getpwnam(h)) != NULL) || + (pwd = getpwuid(getuid())) != NULL) + h = pwd->pw_dir; + else + return pattern; + } + } + else { + /* + * Expand a ~user + */ + if ((pwd = getpwnam((char*) patbuf)) == NULL) + return pattern; + else + h = pwd->pw_dir; + } + + /* Copy the home directory */ + for (b = patbuf; b < eb && *h; *b++ = *h++) + continue; + + /* Append the rest of the pattern */ + while (b < eb && (*b++ = *p++) != EOS) + continue; + *b = EOS; + + return patbuf; +} + + +/* + * The main glob() routine: compiles the pattern (optionally processing + * quotes), calls glob1() to do the real pattern matching, and finally + * sorts the list (unless unsorted operation is requested). Returns 0 + * if things went well, nonzero if errors occurred. + */ +static int +glob0(const Char *pattern, glob_t *pglob, size_t *limit) +{ + const Char *qpatnext; + int err; + size_t oldpathc; + Char *bufnext, c, patbuf[MAXPATHLEN]; + + qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); + oldpathc = pglob->gl_pathc; + bufnext = patbuf; + + /* We don't need to check for buffer overflow any more. */ + while ((c = *qpatnext++) != EOS) { + switch (c) { + case LBRACKET: + c = *qpatnext; + if (c == NOT) + ++qpatnext; + if (*qpatnext == EOS || + g_strchr(qpatnext+1, RBRACKET) == NULL) { + *bufnext++ = LBRACKET; + if (c == NOT) + --qpatnext; + break; + } + *bufnext++ = M_SET; + if (c == NOT) + *bufnext++ = M_NOT; + c = *qpatnext++; + do { + *bufnext++ = CHAR(c); + if (*qpatnext == RANGE && + (c = qpatnext[1]) != RBRACKET) { + *bufnext++ = M_RNG; + *bufnext++ = CHAR(c); + qpatnext += 2; + } + } while ((c = *qpatnext++) != RBRACKET); + pglob->gl_flags |= GLOB_MAGCHAR; + *bufnext++ = M_END; + break; + case QUESTION: + pglob->gl_flags |= GLOB_MAGCHAR; + *bufnext++ = M_ONE; + break; + case STAR: + pglob->gl_flags |= GLOB_MAGCHAR; + /* collapse adjacent stars to one, + * to avoid exponential behavior + */ + if (bufnext == patbuf || bufnext[-1] != M_ALL) + *bufnext++ = M_ALL; + break; + default: + *bufnext++ = CHAR(c); + break; + } + } + *bufnext = EOS; +#ifdef DEBUG + qprintf("glob0:", patbuf); +#endif + + if ((err = glob1(patbuf, pglob, limit)) != 0) + return(err); + + /* + * If there was no match we are going to append the pattern + * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified + * and the pattern did not contain any magic characters + * GLOB_NOMAGIC is there just for compatibility with csh. + */ + if (pglob->gl_pathc == oldpathc) { + if (((pglob->gl_flags & GLOB_NOCHECK) || + ((pglob->gl_flags & GLOB_NOMAGIC) && + !(pglob->gl_flags & GLOB_MAGCHAR)))) + return(globextend(pattern, pglob, limit)); + else + return(GLOB_NOMATCH); + } + if (!(pglob->gl_flags & GLOB_NOSORT)) + qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc, + pglob->gl_pathc - oldpathc, sizeof(char *), compare); + return(0); +} + +static int +compare(const void *p, const void *q) +{ + return(strcmp(*(char **)p, *(char **)q)); +} + +static int +glob1(Char *pattern, glob_t *pglob, size_t *limit) +{ + Char pathbuf[MAXPATHLEN]; + + /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ + if (*pattern == EOS) + return(0); + return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, + pattern, pglob, limit)); +} + +/* + * The functions glob2 and glob3 are mutually recursive; there is one level + * of recursion for each segment in the pattern that contains one or more + * meta characters. + */ +static int +glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, + glob_t *pglob, size_t *limit) +{ + struct stat sb; + Char *p, *q; + int anymeta; + + /* + * Loop over pattern segments until end of pattern or until + * segment with meta character found. + */ + for (anymeta = 0;;) { + if (*pattern == EOS) { /* End of pattern? */ + *pathend = EOS; + if (g_lstat(pathbuf, &sb, pglob)) + return(0); + + if (((pglob->gl_flags & GLOB_MARK) && + pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) + || (S_ISLNK(sb.st_mode) && + (g_stat(pathbuf, &sb, pglob) == 0) && + S_ISDIR(sb.st_mode)))) { + if (pathend + 1 > pathend_last) + return (GLOB_ABORTED); + *pathend++ = SEP; + *pathend = EOS; + } + ++pglob->gl_matchc; + return(globextend(pathbuf, pglob, limit)); + } + + /* Find end of next segment, copy tentatively to pathend. */ + q = pathend; + p = pattern; + while (*p != EOS && *p != SEP) { + if (ismeta(*p)) + anymeta = 1; + if (q + 1 > pathend_last) + return (GLOB_ABORTED); + *q++ = *p++; + } + + if (!anymeta) { /* No expansion, do next segment. */ + pathend = q; + pattern = p; + while (*pattern == SEP) { + if (pathend + 1 > pathend_last) + return (GLOB_ABORTED); + *pathend++ = *pattern++; + } + } else /* Need expansion, recurse. */ + return(glob3(pathbuf, pathend, pathend_last, pattern, p, + pglob, limit)); + } + /* NOTREACHED */ +} + +static int +glob3(Char *pathbuf, Char *pathend, Char *pathend_last, + Char *pattern, Char *restpattern, + glob_t *pglob, size_t *limit) +{ + struct dirent *dp; + DIR *dirp; + int err; + char buf[MAXPATHLEN]; + + /* + * The readdirfunc declaration can't be prototyped, because it is + * assigned, below, to two functions which are prototyped in glob.h + * and dirent.h as taking pointers to differently typed opaque + * structures. + */ + struct dirent *(*readdirfunc)(); + + if (pathend > pathend_last) + return (GLOB_ABORTED); + *pathend = EOS; + errno = 0; + + if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { + /* TODO: don't call for ENOENT or ENOTDIR? */ + if (pglob->gl_errfunc) { + if (g_Ctoc(pathbuf, buf, sizeof(buf))) + return (GLOB_ABORTED); + if (pglob->gl_errfunc(buf, errno) || + pglob->gl_flags & GLOB_ERR) + return (GLOB_ABORTED); + } + return(0); + } + + err = 0; + + /* Search directory for matching names. */ + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + readdirfunc = pglob->gl_readdir; + else + readdirfunc = readdir; + while ((dp = (*readdirfunc)(dirp))) { + char *sc; + Char *dc; + wchar_t wc; + size_t clen; + mbstate_t mbs; + + /* Initial DOT must be matched literally. */ + if (dp->d_name[0] == DOT && *pattern != DOT) + continue; + memset(&mbs, 0, sizeof(mbs)); + dc = pathend; + sc = dp->d_name; + while (dc < pathend_last) { + clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) { + wc = *sc; + clen = 1; + memset(&mbs, 0, sizeof(mbs)); + } + if ((*dc++ = wc) == EOS) + break; + sc += clen; + } + if (!match(pathend, pattern, restpattern)) { + *pathend = EOS; + continue; + } + err = glob2(pathbuf, --dc, pathend_last, restpattern, + pglob, limit); + if (err) + break; + } + + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + (*pglob->gl_closedir)(dirp); + else + closedir(dirp); + return(err); +} + + +/* + * Extend the gl_pathv member of a glob_t structure to accomodate a new item, + * add the new item, and update gl_pathc. + * + * This assumes the BSD realloc, which only copies the block when its size + * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic + * behavior. + * + * Return 0 if new item added, error code if memory couldn't be allocated. + * + * Invariant of the glob_t structure: + * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and + * gl_pathv points to (gl_offs + gl_pathc + 1) items. + */ +static int +globextend(const Char *path, glob_t *pglob, size_t *limit) +{ + char **pathv; + size_t i, newsize, len; + char *copy; + const Char *p; + + if (*limit && pglob->gl_pathc > *limit) { + errno = 0; + return (GLOB_NOSPACE); + } + + newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); + pathv = pglob->gl_pathv ? + realloc((char *)pglob->gl_pathv, newsize) : + malloc(newsize); + if (pathv == NULL) { + if (pglob->gl_pathv) { + free(pglob->gl_pathv); + pglob->gl_pathv = NULL; + } + return(GLOB_NOSPACE); + } + + if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { + /* first time around -- clear initial gl_offs items */ + pathv += pglob->gl_offs; + for (i = pglob->gl_offs + 1; --i > 0; ) + *--pathv = NULL; + } + pglob->gl_pathv = pathv; + + for (p = path; *p++;) + continue; + len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ + if ((copy = malloc(len)) != NULL) { + if (g_Ctoc(path, copy, len)) { + free(copy); + return (GLOB_NOSPACE); + } + pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; + } + pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; + return(copy == NULL ? GLOB_NOSPACE : 0); +} + +/* + * pattern matching function for filenames. Each occurrence of the * + * pattern causes a recursion level. + */ +static int +match(Char *name, Char *pat, Char *patend) +{ + int ok, negate_range; + Char c, k; + + while (pat < patend) { + c = *pat++; + switch (c & M_MASK) { + case M_ALL: + if (pat == patend) + return(1); + do + if (match(name, pat, patend)) + return(1); + while (*name++ != EOS); + return(0); + case M_ONE: + if (*name++ == EOS) + return(0); + break; + case M_SET: + ok = 0; + if ((k = *name++) == EOS) + return(0); + if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) + ++pat; + while (((c = *pat++) & M_MASK) != M_END) + if ((*pat & M_MASK) == M_RNG) { + if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1; + pat += 2; + } else if (c == k) + ok = 1; + if (ok == negate_range) + return(0); + break; + default: + if (*name++ != c) + return(0); + break; + } + } + return(*name == EOS); +} + +/* Free allocated data belonging to a glob_t structure. */ +void +globfree(glob_t *pglob) +{ + size_t i; + char **pp; + + if (pglob->gl_pathv != NULL) { + pp = pglob->gl_pathv + pglob->gl_offs; + for (i = pglob->gl_pathc; i--; ++pp) + if (*pp) + free(*pp); + free(pglob->gl_pathv); + pglob->gl_pathv = NULL; + } +} + +static DIR * +g_opendir(Char *str, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (!*str) + strcpy(buf, "."); + else { + if (g_Ctoc(str, buf, sizeof(buf))) + return (NULL); + } + + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_opendir)(buf)); + + return(opendir(buf)); +} + +static int +g_lstat(Char *fn, struct stat *sb, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (g_Ctoc(fn, buf, sizeof(buf))) { + errno = ENAMETOOLONG; + return (-1); + } + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_lstat)(buf, sb)); + return(lstat(buf, sb)); +} + +static int +g_stat(Char *fn, struct stat *sb, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (g_Ctoc(fn, buf, sizeof(buf))) { + errno = ENAMETOOLONG; + return (-1); + } + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_stat)(buf, sb)); + return(stat(buf, sb)); +} + +static const Char * +g_strchr(const Char *str, wchar_t ch) +{ + + do { + if (*str == ch) + return (str); + } while (*str++); + return (NULL); +} + +static int +g_Ctoc(const Char *str, char *buf, size_t len) +{ + mbstate_t mbs; + size_t clen; + + memset(&mbs, 0, sizeof(mbs)); + while (len >= MB_CUR_MAX) { + clen = wcrtomb(buf, *str, &mbs); + if (clen == (size_t)-1) + return (1); + if (*str == L'\0') + return (0); + str++; + buf += clen; + len -= clen; + } + return (1); +} + +#ifdef DEBUG +static void +qprintf(const char *str, Char *s) +{ + Char *p; + + (void)printf("%s:\n", str); + for (p = s; *p; p++) + (void)printf("%c", CHAR(*p)); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", *p & M_PROTECT ? '"' : ' '); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", ismeta(*p) ? '_' : ' '); + (void)printf("\n"); +} +#endif diff --git a/p4a/pythonforandroid/recipes/libxml2/glob.h b/p4a/pythonforandroid/recipes/libxml2/glob.h new file mode 100644 index 00000000..351b6c46 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxml2/glob.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)glob.h 8.1 (Berkeley) 6/2/93 + * $FreeBSD$ + */ + +#ifndef _GLOB_H_ +#define _GLOB_H_ + +#include +#include + +#ifndef _SIZE_T_DECLARED +typedef __size_t size_t; +#define _SIZE_T_DECLARED +#endif + +struct stat; +typedef struct { + size_t gl_pathc; /* Count of total paths so far. */ + size_t gl_matchc; /* Count of paths matching pattern. */ + size_t gl_offs; /* Reserved at beginning of gl_pathv. */ + int gl_flags; /* Copy of flags parameter to glob. */ + char **gl_pathv; /* List of paths matching pattern. */ + /* Copy of errfunc parameter to glob. */ + int (*gl_errfunc)(const char *, int); + + /* + * Alternate filesystem access methods for glob; replacement + * versions of closedir(3), readdir(3), opendir(3), stat(2) + * and lstat(2). + */ + void (*gl_closedir)(void *); + struct dirent *(*gl_readdir)(void *); + void *(*gl_opendir)(const char *); + int (*gl_lstat)(const char *, struct stat *); + int (*gl_stat)(const char *, struct stat *); +} glob_t; + +#if __POSIX_VISIBLE >= 199209 +/* Believed to have been introduced in 1003.2-1992 */ +#define GLOB_APPEND 0x0001 /* Append to output from previous call. */ +#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ +#define GLOB_ERR 0x0004 /* Return on error. */ +#define GLOB_MARK 0x0008 /* Append / to matching directories. */ +#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ +#define GLOB_NOSORT 0x0020 /* Don't sort. */ +#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ + +/* Error values returned by glob(3) */ +#define GLOB_NOSPACE (-1) /* Malloc call failed. */ +#define GLOB_ABORTED (-2) /* Unignored error. */ +#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ +#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */ +#endif /* __POSIX_VISIBLE >= 199209 */ + +#if __BSD_VISIBLE +#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ +#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ +#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ +#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ +#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ +#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ +#define GLOB_LIMIT 0x1000 /* limit number of returned paths */ + +/* source compatibility, these are the old names */ +#define GLOB_MAXPATH GLOB_LIMIT +#define GLOB_ABEND GLOB_ABORTED +#endif /* __BSD_VISIBLE */ + +__BEGIN_DECLS +int glob(const char *, int, int (*)(const char *, int), glob_t *); +void globfree(glob_t *); +__END_DECLS + +#endif /* !_GLOB_H_ */ diff --git a/p4a/pythonforandroid/recipes/libxslt/__init__.py b/p4a/pythonforandroid/recipes/libxslt/__init__.py new file mode 100644 index 00000000..076d6cc6 --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxslt/__init__.py @@ -0,0 +1,74 @@ +from pythonforandroid.recipe import Recipe +from pythonforandroid.toolchain import shprint, shutil, current_directory +from os.path import exists, join +import sh + + +class LibxsltRecipe(Recipe): + version = '1.1.32' + url = 'http://xmlsoft.org/sources/libxslt-{version}.tar.gz' + depends = ['libxml2'] + patches = ['fix-dlopen.patch'] + + call_hostpython_via_targetpython = False + + def should_build(self, arch): + return not exists( + join(self.get_build_dir(arch.arch), + 'libxslt', '.libs', 'libxslt.a')) + + def build_arch(self, arch): + super(LibxsltRecipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + build_dir = self.get_build_dir(arch.arch) + with current_directory(build_dir): + # If the build is done with /bin/sh things blow up, + # try really hard to use bash + libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx) + libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch) + build_arch = shprint(sh.gcc, '-dumpmachine').stdout.decode( + 'utf-8').split('\n')[0] + + if not exists('configure'): + shprint(sh.Command('./autogen.sh'), _env=env) + shprint(sh.Command('autoreconf'), '-vif', _env=env) + shprint(sh.Command('./configure'), + '--build=' + build_arch, + '--host=' + arch.command_prefix, + '--target=' + arch.command_prefix, + '--without-plugins', + '--without-debug', + '--without-python', + '--without-crypto', + '--with-libxml-src=' + libxml2_build_dir, + '--disable-shared', + _env=env) + shprint(sh.make, "V=1", _env=env) + + shutil.copyfile('libxslt/.libs/libxslt.a', + join(self.ctx.libs_dir, 'libxslt.a')) + shutil.copyfile('libexslt/.libs/libexslt.a', + join(self.ctx.libs_dir, 'libexslt.a')) + + def get_recipe_env(self, arch): + env = super(LibxsltRecipe, self).get_recipe_env(arch) + env['CONFIG_SHELL'] = '/bin/bash' + env['SHELL'] = '/bin/bash' + + libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx) + libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch) + libxml2_libs_dir = join(libxml2_build_dir, '.libs') + + env['CFLAGS'] = ' '.join([ + env['CFLAGS'], + '-I' + libxml2_build_dir, + '-I' + join(libxml2_build_dir, 'include', 'libxml'), + '-I' + self.get_build_dir(arch.arch), + ]) + env['LDFLAGS'] += ' -L' + libxml2_libs_dir + env['LIBS'] = '-lxml2 -lz -lm' + + return env + + +recipe = LibxsltRecipe() diff --git a/p4a/pythonforandroid/recipes/libxslt/fix-dlopen.patch b/p4a/pythonforandroid/recipes/libxslt/fix-dlopen.patch new file mode 100644 index 00000000..34d56b6e --- /dev/null +++ b/p4a/pythonforandroid/recipes/libxslt/fix-dlopen.patch @@ -0,0 +1,11 @@ +--- libxslt-1.1.27.orig/python/libxsl.py 2012-09-04 16:26:23.000000000 +0200 ++++ libxslt-1.1.27/python/libxsl.py 2013-07-29 15:11:04.182227378 +0200 +@@ -4,7 +4,7 @@ + # loader to work in that mode if feasible + # + import sys +-if not hasattr(sys,'getdlopenflags'): ++if True: + import libxml2mod + import libxsltmod + import libxml2 diff --git a/p4a/pythonforandroid/recipes/libzbar/__init__.py b/p4a/pythonforandroid/recipes/libzbar/__init__.py new file mode 100644 index 00000000..43ae34cc --- /dev/null +++ b/p4a/pythonforandroid/recipes/libzbar/__init__.py @@ -0,0 +1,57 @@ +import os +from pythonforandroid.toolchain import shprint, current_directory +from pythonforandroid.recipe import Recipe +from multiprocessing import cpu_count +import sh + + +class LibZBarRecipe(Recipe): + + version = '0.10' + + url = 'https://github.com/ZBar/ZBar/archive/{version}.zip' + + depends = ['libiconv'] + + patches = ["werror.patch"] + + def should_build(self, arch): + return not os.path.exists( + os.path.join(self.ctx.get_libs_dir(arch.arch), 'libzbar.so')) + + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + env = super(LibZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc) + libiconv = self.get_recipe('libiconv', self.ctx) + libiconv_dir = libiconv.get_build_dir(arch.arch) + env['CFLAGS'] += ' -I' + os.path.join(libiconv_dir, 'include') + env['LIBS'] = env.get('LIBS', '') + ' -landroid -liconv' + return env + + def build_arch(self, arch): + super(LibZBarRecipe, self).build_arch(arch) + env = self.get_recipe_env(arch) + with current_directory(self.get_build_dir(arch.arch)): + shprint(sh.Command('autoreconf'), '-vif', _env=env) + shprint( + sh.Command('./configure'), + '--host=' + arch.toolchain_prefix, + '--target=' + arch.toolchain_prefix, + '--prefix=' + self.ctx.get_python_install_dir(), + # Python bindings are compiled in a separated recipe + '--with-python=no', + '--with-gtk=no', + '--with-qt=no', + '--with-x=no', + '--with-jpeg=no', + '--with-imagemagick=no', + '--enable-pthread=no', + '--enable-video=no', + '--enable-shared=yes', + '--enable-static=no', + _env=env) + shprint(sh.make, '-j' + str(cpu_count()), _env=env) + libs = ['zbar/.libs/libzbar.so'] + self.install_libs(arch, *libs) + + +recipe = LibZBarRecipe() diff --git a/p4a/pythonforandroid/recipes/libzbar/werror.patch b/p4a/pythonforandroid/recipes/libzbar/werror.patch new file mode 100644 index 00000000..9fe5d36a --- /dev/null +++ b/p4a/pythonforandroid/recipes/libzbar/werror.patch @@ -0,0 +1,13 @@ +diff --git a/configure.ac b/configure.ac +index 256aedb..727caba 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -3,7 +3,7 @@ AC_PREREQ([2.61]) + AC_INIT([zbar], [0.10], [spadix@users.sourceforge.net]) + AC_CONFIG_AUX_DIR(config) + AC_CONFIG_MACRO_DIR(config) +-AM_INIT_AUTOMAKE([1.10 -Wall -Werror foreign subdir-objects std-options dist-bzip2]) ++AM_INIT_AUTOMAKE([1.10 -Wall foreign subdir-objects std-options dist-bzip2]) + AC_CONFIG_HEADERS([include/config.h]) + AC_CONFIG_SRCDIR(zbar/scanner.c) + LT_PREREQ([2.2]) diff --git a/p4a/pythonforandroid/recipes/libzmq/__init__.py b/p4a/pythonforandroid/recipes/libzmq/__init__.py index 1ebebf7b..b33f3ac6 100644 --- a/p4a/pythonforandroid/recipes/libzmq/__init__.py +++ b/p4a/pythonforandroid/recipes/libzmq/__init__.py @@ -7,7 +7,7 @@ import sh class LibZMQRecipe(Recipe): version = '4.1.4' url = 'http://download.zeromq.org/zeromq-{version}.tar.gz' - depends = ['python2'] + depends = [] def should_build(self, arch): super(LibZMQRecipe, self).should_build(arch) @@ -51,7 +51,7 @@ class LibZMQRecipe(Recipe): # Copy libgnustl_shared.so with current_directory(self.get_build_dir(arch.arch)): sh.cp( - "{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/libgnustl_shared.so".format(ctx=self.ctx,arch=arch), + "{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/libgnustl_shared.so".format(ctx=self.ctx, arch=arch), self.ctx.get_libs_dir(arch.arch) ) diff --git a/p4a/pythonforandroid/recipes/lxml/__init__.py b/p4a/pythonforandroid/recipes/lxml/__init__.py new file mode 100644 index 00000000..6d4b91c2 --- /dev/null +++ b/p4a/pythonforandroid/recipes/lxml/__init__.py @@ -0,0 +1,66 @@ +from pythonforandroid.recipe import Recipe, CompiledComponentsPythonRecipe +from os.path import exists, join +from os import uname + + +class LXMLRecipe(CompiledComponentsPythonRecipe): + version = '4.2.5' + url = 'https://pypi.python.org/packages/source/l/lxml/lxml-{version}.tar.gz' # noqa + depends = ['librt', 'libxml2', 'libxslt', 'setuptools'] + name = 'lxml' + + call_hostpython_via_targetpython = False # Due to setuptools + + def should_build(self, arch): + super(LXMLRecipe, self).should_build(arch) + + py_ver = self.ctx.python_recipe.major_minor_version_string + build_platform = '{system}-{machine}'.format( + system=uname()[0], machine=uname()[-1]).lower() + build_dir = join(self.get_build_dir(arch.arch), 'build', + 'lib.' + build_platform + '-' + py_ver, 'lxml') + py_libs = ['_elementpath.so', 'builder.so', 'etree.so', 'objectify.so'] + + return not all([exists(join(build_dir, lib)) for lib in py_libs]) + + def get_recipe_env(self, arch): + env = super(LXMLRecipe, self).get_recipe_env(arch) + + # libxslt flags + libxslt_recipe = Recipe.get_recipe('libxslt', self.ctx) + libxslt_build_dir = libxslt_recipe.get_build_dir(arch.arch) + + cflags = ' -I' + libxslt_build_dir + cflags += ' -I' + join(libxslt_build_dir, 'libxslt') + cflags += ' -I' + join(libxslt_build_dir, 'libexslt') + + env['LDFLAGS'] += ' -L' + join(libxslt_build_dir, 'libxslt', '.libs') + env['LDFLAGS'] += ' -L' + join(libxslt_build_dir, 'libexslt', '.libs') + env['LIBS'] = '-lxslt -lexslt' + + # libxml2 flags + libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx) + libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch) + libxml2_libs_dir = join(libxml2_build_dir, '.libs') + + cflags += ' -I' + libxml2_build_dir + cflags += ' -I' + join(libxml2_build_dir, 'include') + cflags += ' -I' + join(libxml2_build_dir, 'include', 'libxml') + cflags += ' -I' + self.get_build_dir(arch.arch) + env['LDFLAGS'] += ' -L' + libxml2_libs_dir + env['LIBS'] += ' -lxml2' + + # android's ndk flags + ndk_lib_dir = join(self.ctx.ndk_platform, 'usr', 'lib') + ndk_include_dir = join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include') + cflags += ' -I' + ndk_include_dir + env['LDFLAGS'] += ' -L' + ndk_lib_dir + env['LIBS'] += ' -lz -lm -lc' + + if cflags not in env['CFLAGS']: + env['CFLAGS'] += cflags + + return env + + +recipe = LXMLRecipe() diff --git a/p4a/pythonforandroid/recipes/m2crypto/__init__.py b/p4a/pythonforandroid/recipes/m2crypto/__init__.py index 04754a63..653eeca8 100644 --- a/p4a/pythonforandroid/recipes/m2crypto/__init__.py +++ b/p4a/pythonforandroid/recipes/m2crypto/__init__.py @@ -1,46 +1,40 @@ -from pythonforandroid.toolchain import PythonRecipe, shprint, shutil, current_directory -from os.path import join, exists +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from pythonforandroid.toolchain import current_directory +from pythonforandroid.logger import shprint, info +import glob import sh -class M2CryptoRecipe(PythonRecipe): - version = '0.24.0' + +class M2CryptoRecipe(CompiledComponentsPythonRecipe): + version = '0.30.1' url = 'https://pypi.python.org/packages/source/M/M2Crypto/M2Crypto-{version}.tar.gz' - #md5sum = '89557730e245294a6cab06de8ad4fb42' - depends = ['openssl', 'hostpython2', 'python2', 'setuptools'] + depends = ['openssl', 'setuptools'] site_packages_name = 'M2Crypto' call_hostpython_via_targetpython = False - def build_arch(self, arch): + def build_compiled_components(self, arch): + info('Building compiled components in {}'.format(self.name)) + env = self.get_recipe_env(arch) with current_directory(self.get_build_dir(arch.arch)): # Build M2Crypto hostpython = sh.Command(self.hostpython_location) - r = self.get_recipe('openssl', self.ctx) - openssl_dir = r.get_build_dir(arch.arch) - shprint(hostpython, - 'setup.py', - 'build_ext', + if self.install_in_hostpython: + shprint(hostpython, 'setup.py', 'clean', '--all', _env=env) + shprint(hostpython, 'setup.py', self.build_cmd, '-p' + arch.arch, '-c' + 'unix', - '--openssl=' + openssl_dir - , _env=env) - # Install M2Crypto - super(M2CryptoRecipe, self).build_arch(arch) + '-o' + env['OPENSSL_BUILD_PATH'], + '-L' + env['OPENSSL_BUILD_PATH'], + _env=env, *self.setup_extra_args) + build_dir = glob.glob('build/lib.*')[0] + shprint(sh.find, build_dir, '-name', '"*.o"', '-exec', + env['STRIP'], '{}', ';', _env=env) def get_recipe_env(self, arch): env = super(M2CryptoRecipe, self).get_recipe_env(arch) - r = self.get_recipe('openssl', self.ctx) - openssl_dir = r.get_build_dir(arch.arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \ - ' -I' + join(openssl_dir, 'include') - # Set linker to use the correct gcc - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -L' + openssl_dir + \ - ' -lpython2.7' + \ - ' -lssl' + r.version + \ - ' -lcrypto' + r.version + env['OPENSSL_BUILD_PATH'] = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch) return env + recipe = M2CryptoRecipe() diff --git a/p4a/pythonforandroid/recipes/msgpack-python/__init__.py b/p4a/pythonforandroid/recipes/msgpack-python/__init__.py index 393e6b40..cdd024b9 100644 --- a/p4a/pythonforandroid/recipes/msgpack-python/__init__.py +++ b/p4a/pythonforandroid/recipes/msgpack-python/__init__.py @@ -1,12 +1,11 @@ -import os -import sh from pythonforandroid.recipe import CythonRecipe class MsgPackRecipe(CythonRecipe): version = '0.4.7' url = 'https://pypi.python.org/packages/source/m/msgpack-python/msgpack-python-{version}.tar.gz' - depends = [('python2', 'python3crystax'), "setuptools"] + depends = ["setuptools"] call_hostpython_via_targetpython = False + recipe = MsgPackRecipe() diff --git a/p4a/pythonforandroid/recipes/mysqldb/__init__.py b/p4a/pythonforandroid/recipes/mysqldb/__init__.py index b217bbb6..f0845856 100644 --- a/p4a/pythonforandroid/recipes/mysqldb/__init__.py +++ b/p4a/pythonforandroid/recipes/mysqldb/__init__.py @@ -3,50 +3,50 @@ from os.path import join class MysqldbRecipe(CompiledComponentsPythonRecipe): - name = 'mysqldb' - version = '1.2.5' - url = 'https://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-{version}.zip' - site_packages_name = 'MySQLdb' + name = 'mysqldb' + version = '1.2.5' + url = 'https://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-{version}.zip' + site_packages_name = 'MySQLdb' - depends = ['python2', 'setuptools', 'libmysqlclient'] + depends = ['setuptools', 'libmysqlclient'] - patches = ['override-mysql-config.patch', - 'disable-zip.patch'] + patches = ['override-mysql-config.patch', + 'disable-zip.patch'] - # call_hostpython_via_targetpython = False + # call_hostpython_via_targetpython = False - def convert_newlines(self, filename): - print('converting newlines in {}'.format(filename)) - with open(filename, 'rb') as f: - data = f.read() - with open(filename, 'wb') as f: - f.write(data.replace(b'\r\n', b'\n').replace(b'\r', b'\n')) + def convert_newlines(self, filename): + print('converting newlines in {}'.format(filename)) + with open(filename, 'rb') as f: + data = f.read() + with open(filename, 'wb') as f: + f.write(data.replace(b'\r\n', b'\n').replace(b'\r', b'\n')) - def prebuild_arch(self, arch): - super(MysqldbRecipe, self).prebuild_arch(arch) - setupbase = join(self.get_build_dir(arch.arch), 'setup') - self.convert_newlines(setupbase + '.py') - self.convert_newlines(setupbase + '_posix.py') + def prebuild_arch(self, arch): + super(MysqldbRecipe, self).prebuild_arch(arch) + setupbase = join(self.get_build_dir(arch.arch), 'setup') + self.convert_newlines(setupbase + '.py') + self.convert_newlines(setupbase + '_posix.py') - def get_recipe_env(self, arch=None): - env = super(MysqldbRecipe, self).get_recipe_env(arch) + def get_recipe_env(self, arch=None): + env = super(MysqldbRecipe, self).get_recipe_env(arch) - hostpython = self.get_recipe('hostpython2', self.ctx) - # TODO: fix hardcoded path - env['PYTHONPATH'] = (join(hostpython.get_build_dir(arch.arch), - 'build', 'lib.linux-x86_64-2.7') + - ':' + env.get('PYTHONPATH', '')) + hostpython = self.get_recipe('hostpython2', self.ctx) + # TODO: fix hardcoded path + env['PYTHONPATH'] = (join(hostpython.get_build_dir(arch.arch), + 'build', 'lib.linux-x86_64-2.7') + + ':' + env.get('PYTHONPATH', '')) - libmysql = self.get_recipe('libmysqlclient', self.ctx) - mydir = join(libmysql.get_build_dir(arch.arch), 'libmysqlclient') - # env['CFLAGS'] += ' -I' + join(mydir, 'include') - # env['LDFLAGS'] += ' -L' + join(mydir) - libdir = self.ctx.get_libs_dir(arch.arch) - env['MYSQL_libs'] = env['MYSQL_libs_r'] = '-L' + libdir + ' -lmysql' - env['MYSQL_cflags'] = env['MYSQL_include'] = '-I' + join(mydir, - 'include') + libmysql = self.get_recipe('libmysqlclient', self.ctx) + mydir = join(libmysql.get_build_dir(arch.arch), 'libmysqlclient') + # env['CFLAGS'] += ' -I' + join(mydir, 'include') + # env['LDFLAGS'] += ' -L' + join(mydir) + libdir = self.ctx.get_libs_dir(arch.arch) + env['MYSQL_libs'] = env['MYSQL_libs_r'] = '-L' + libdir + ' -lmysql' + env['MYSQL_cflags'] = env['MYSQL_include'] = '-I' + join(mydir, + 'include') - return env + return env recipe = MysqldbRecipe() diff --git a/p4a/pythonforandroid/recipes/ndghttpsclient b/p4a/pythonforandroid/recipes/ndghttpsclient index bd971fcd..35e996f0 100644 --- a/p4a/pythonforandroid/recipes/ndghttpsclient +++ b/p4a/pythonforandroid/recipes/ndghttpsclient @@ -1,4 +1,4 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class NdgHttpsClientRecipe(PythonRecipe): version = '0.4.0' diff --git a/p4a/pythonforandroid/recipes/netifaces/__init__.py b/p4a/pythonforandroid/recipes/netifaces/__init__.py index 74273fc5..8ad13820 100644 --- a/p4a/pythonforandroid/recipes/netifaces/__init__.py +++ b/p4a/pythonforandroid/recipes/netifaces/__init__.py @@ -3,25 +3,17 @@ from pythonforandroid.recipe import CompiledComponentsPythonRecipe class NetifacesRecipe(CompiledComponentsPythonRecipe): - version = '0.10.4' + version = '0.10.9' - url = 'https://pypi.python.org/packages/18/fa/dd13d4910aea339c0bb87d2b3838d8fd923c11869b1f6e741dbd0ff3bc00/netifaces-{version}.tar.gz' + url = 'https://files.pythonhosted.org/packages/source/n/netifaces/netifaces-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools'] + depends = ['setuptools'] + + patches = ['fix-build.patch'] site_packages_name = 'netifaces' call_hostpython_via_targetpython = False - def get_recipe_env(self, arch): - env = super(NetifacesRecipe, self).get_recipe_env(arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' - # Set linker to use the correct gcc - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \ - ' -lpython2.7' - return env - recipe = NetifacesRecipe() diff --git a/p4a/pythonforandroid/recipes/netifaces/fix-build.patch b/p4a/pythonforandroid/recipes/netifaces/fix-build.patch new file mode 100644 index 00000000..3404c4fe --- /dev/null +++ b/p4a/pythonforandroid/recipes/netifaces/fix-build.patch @@ -0,0 +1,11 @@ +--- netifaces/setup.py.orig 2018-05-02 09:45:09.000000000 +0200 ++++ netifaces/setup.py 2018-12-11 14:12:02.785808692 +0100 +@@ -55,7 +55,7 @@ + self.check_requirements() + build_ext.build_extensions(self) + +- def test_build(self, contents, link=True, execute=False, libraries=None, ++ def test_build(self, contents, link=False, execute=False, libraries=None, + include_dirs=None, library_dirs=None): + name = os.path.join(self.build_temp, 'conftest-%s.c' % self.conftestidx) + self.conftestidx += 1 diff --git a/p4a/pythonforandroid/recipes/numpy/__init__.py b/p4a/pythonforandroid/recipes/numpy/__init__.py index 28e4be1d..6b6e6b39 100644 --- a/p4a/pythonforandroid/recipes/numpy/__init__.py +++ b/p4a/pythonforandroid/recipes/numpy/__init__.py @@ -1,26 +1,58 @@ - -from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, warning +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from multiprocessing import cpu_count +from os.path import join class NumpyRecipe(CompiledComponentsPythonRecipe): - - version = '1.9.2' - url = 'http://pypi.python.org/packages/source/n/numpy/numpy-{version}.tar.gz' - site_packages_name= 'numpy' - depends = ['python2'] + version = '1.15.1' + url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip' + site_packages_name = 'numpy' + depends = [('python2', 'python3', 'python3crystax')] - patches = ['patches/fix-numpy.patch', - 'patches/prevent_libs_check.patch', - 'patches/ar.patch', - 'patches/lib.patch'] + patches = [ + join('patches', 'fix-numpy.patch'), + join('patches', 'prevent_libs_check.patch'), + join('patches', 'ar.patch'), + join('patches', 'lib.patch'), + join('patches', 'python-fixes.patch') + ] - def prebuild_arch(self, arch): - super(NumpyRecipe, self).prebuild_arch(arch) + def build_compiled_components(self, arch): + self.setup_extra_args = ['-j', str(cpu_count())] + super(NumpyRecipe, self).build_compiled_components(arch) + self.setup_extra_args = [] - # AND: Fix this warning! - warning('Numpy is built assuming the archiver name is ' - 'arm-linux-androideabi-ar, which may not always be true!') + def rebuild_compiled_components(self, arch, env): + self.setup_extra_args = ['-j', str(cpu_count())] + super(NumpyRecipe, self).rebuild_compiled_components(arch, env) + self.setup_extra_args = [] + + def get_recipe_env(self, arch): + env = super(NumpyRecipe, self).get_recipe_env(arch) + + flags = " -L{} --sysroot={}".format( + join(self.ctx.ndk_platform, 'usr', 'lib'), + self.ctx.ndk_platform + ) + + py_ver = self.ctx.python_recipe.major_minor_version_string + py_inc_dir = self.ctx.python_recipe.include_root(arch.arch) + py_lib_dir = self.ctx.python_recipe.link_root(arch.arch) + if self.ctx.ndk == 'crystax': + src_dir = join(self.ctx.ndk_dir, 'sources') + flags += " -I{}".format(join(src_dir, 'crystax', 'include')) + flags += " -L{}".format(join(src_dir, 'crystax', 'libs', arch.arch)) + flags += ' -I{}'.format(py_inc_dir) + flags += ' -L{} -lpython{}'.format(py_lib_dir, py_ver) + if 'python3' in self.ctx.python_recipe.name: + flags += 'm' + + if flags not in env['CC']: + env['CC'] += flags + if flags not in env['LD']: + env['LD'] += flags + ' -shared' + return env recipe = NumpyRecipe() diff --git a/p4a/pythonforandroid/recipes/numpy/patches/ar.patch b/p4a/pythonforandroid/recipes/numpy/patches/ar.patch index c579d5e9..ddb096cc 100644 --- a/p4a/pythonforandroid/recipes/numpy/patches/ar.patch +++ b/p4a/pythonforandroid/recipes/numpy/patches/ar.patch @@ -1,11 +1,44 @@ ---- a/numpy/distutils/unixccompiler.py 2015-02-01 17:38:21.000000000 +0100 -+++ b/numpy/distutils/unixccompiler.py 2015-07-08 17:21:05.742468485 +0200 -@@ -82,6 +82,8 @@ - pass - self.mkpath(os.path.dirname(output_filename)) - tmp_objects = objects + self.objects -+ from os import environ -+ self.archiver[0] = 'arm-linux-androideabi-ar' +diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py +index 632bcb4..c1e0dd5 100644 +--- a/numpy/core/code_generators/generate_umath.py ++++ b/numpy/core/code_generators/generate_umath.py +@@ -970,6 +970,7 @@ def make_arrays(funcdict): + funclist.append('%s_%s' % (tname, name)) + if t.simd is not None: + for vt in t.simd: ++ continue + code2list.append(textwrap.dedent("""\ + #ifdef HAVE_ATTRIBUTE_TARGET_{ISA} + if (npy_cpu_supports("{isa}")) {{ +diff --git a/numpy/distutils/ccompiler.py b/numpy/distutils/ccompiler.py +index b03fb96..f9e6cd0 100644 +--- a/numpy/distutils/ccompiler.py ++++ b/numpy/distutils/ccompiler.py +@@ -275,6 +275,7 @@ def CCompiler_compile(self, sources, output_dir=None, macros=None, + self._setup_compile(output_dir, macros, include_dirs, sources, + depends, extra_postargs) + cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) ++ cc_args += os.environ['CFLAGS'].split() + display = "compile options: '%s'" % (' '.join(cc_args)) + if extra_postargs: + display += "\nextra options: '%s'" % (' '.join(extra_postargs)) +diff --git a/numpy/distutils/unixccompiler.py b/numpy/distutils/unixccompiler.py +index 11b2cce..f6dde79 100644 +--- a/numpy/distutils/unixccompiler.py ++++ b/numpy/distutils/unixccompiler.py +@@ -54,6 +54,7 @@ def UnixCCompiler__compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts + deps = [] + + try: ++ self.linker_so = [os.environ['LD']+" "+os.environ['LDFLAGS']] + self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + deps + + extra_postargs, display = display) + except DistutilsExecError: +@@ -111,6 +112,7 @@ def UnixCCompiler_create_static_lib(self, objects, output_libname, while tmp_objects: objects = tmp_objects[:50] tmp_objects = tmp_objects[50:] ++ self.archiver[0] = os.environ['AR'] + display = '%s: adding %d object files to %s' % ( + os.path.basename(self.archiver[0]), + len(objects), output_filename) diff --git a/p4a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch b/p4a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch index 52d447f5..a5e00843 100644 --- a/p4a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch +++ b/p4a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch @@ -1,49 +1,17 @@ -diff --git a/numpy/core/src/multiarray/numpyos.c b/numpy/core/src/multiarray/numpyos.c -index 44b32f4..378e199 100644 ---- a/numpy/core/src/multiarray/numpyos.c -+++ b/numpy/core/src/multiarray/numpyos.c -@@ -165,8 +165,7 @@ ensure_decimal_point(char* buffer, size_t buf_size) - static void - change_decimal_from_locale_to_dot(char* buffer) - { -- struct lconv *locale_data = localeconv(); -- const char *decimal_point = locale_data->decimal_point; -+ const char *decimal_point = "."; - - if (decimal_point[0] != '.' || decimal_point[1] != 0) { - size_t decimal_point_len = strlen(decimal_point); -@@ -448,8 +447,7 @@ NumPyOS_ascii_strtod_plain(const char *s, char** endptr) - NPY_NO_EXPORT double - NumPyOS_ascii_strtod(const char *s, char** endptr) - { -- struct lconv *locale_data = localeconv(); -- const char *decimal_point = locale_data->decimal_point; -+ const char *decimal_point = "."; - size_t decimal_point_len = strlen(decimal_point); - - char buffer[FLOAT_FORMATBUFLEN+1]; -diff --git a/numpy/core/src/private/npy_config.h b/numpy/core/src/private/npy_config.h -index f768c90..4e5d168 100644 ---- a/numpy/core/src/private/npy_config.h -+++ b/numpy/core/src/private/npy_config.h -@@ -41,4 +41,10 @@ - #undef HAVE_ATAN2 - #endif - -+/* Android only */ -+#ifdef ANDROID -+#undef HAVE_LDEXPL -+#undef HAVE_FREXPL -+#endif -+ - #endif diff --git a/numpy/testing/__init__.py b/numpy/testing/__init__.py -index 258cbe9..ce4e0eb 100644 +index a7c8593..007ce26 100644 --- a/numpy/testing/__init__.py +++ b/numpy/testing/__init__.py -@@ -1,16 +1,7 @@ +@@ -1,22 +1,8 @@ -"""Common test support for all numpy test scripts. -- ++# fake tester, android don't have unittest ++class Tester(object): ++ def test(self, *args, **kwargs): ++ pass ++ def bench(self, *args, **kwargs): ++ pass ++test = Tester().test + -This single module should provide all the common functionality for numpy tests -in a single location, so that test scripts can just import it and work right -away. @@ -53,14 +21,14 @@ index 258cbe9..ce4e0eb 100644 - -from unittest import TestCase - --from . import decorators as dec --from .utils import * --from .nosetester import NoseTester as Tester --from .nosetester import run_module_suite -+# fake tester, android don't have unittest -+class Tester(object): -+ def test(self, *args, **kwargs): -+ pass -+ def bench(self, *args, **kwargs): -+ pass - test = Tester().test +-from ._private.utils import * +-from ._private import decorators as dec +-from ._private.nosetester import ( +- run_module_suite, NoseTester as Tester +- ) +- +-__all__ = _private.utils.__all__ + ['TestCase', 'run_module_suite'] +- +-from ._private.pytesttester import PytestTester +-test = PytestTester(__name__) +-del PytestTester diff --git a/p4a/pythonforandroid/recipes/numpy/patches/lib.patch b/p4a/pythonforandroid/recipes/numpy/patches/lib.patch index 3087eb45..194ce51b 100644 --- a/p4a/pythonforandroid/recipes/numpy/patches/lib.patch +++ b/p4a/pythonforandroid/recipes/numpy/patches/lib.patch @@ -1,39 +1,43 @@ ---- a/numpy/linalg/setup.py 2015-07-09 14:15:59.850853336 +0200 -+++ b/numpy/linalg/setup.py 2015-07-09 14:21:59.403889000 +0200 -@@ -37,7 +37,8 @@ - config.add_extension('lapack_lite', - sources = [get_lapack_lite_sources], - depends = ['lapack_litemodule.c'] + lapack_lite_src, -- extra_info = lapack_info -+ extra_info = lapack_info, -+ libraries = ['m'], - ) - - # umath_linalg module -@@ -46,7 +47,7 @@ - sources = [get_lapack_lite_sources], - depends = ['umath_linalg.c.src'] + lapack_lite_src, - extra_info = lapack_info, -- libraries = ['npymath'], -+ libraries = ['npymath','m'], - ) - - return config ---- a/numpy/fft/setup.py 2015-07-09 14:35:22.299888028 +0200 -+++ b/numpy/fft/setup.py 2015-07-09 14:33:54.858392578 +0200 -@@ -9,7 +9,8 @@ +diff --git a/numpy/fft/setup.py b/numpy/fft/setup.py +index cd99a82d7..e614ecd07 100644 +--- a/numpy/fft/setup.py ++++ b/numpy/fft/setup.py +@@ -9,7 +9,8 @@ def configuration(parent_package='',top_path=None): # Configure fftpack_lite config.add_extension('fftpack_lite', - sources=['fftpack_litemodule.c', 'fftpack.c'] + sources=['fftpack_litemodule.c', 'fftpack.c'], -+ libraries = ['m'] ++ libraries=['m'] ) + return config +diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py +index 66c07c9e1..d34bd930a 100644 +--- a/numpy/linalg/setup.py ++++ b/numpy/linalg/setup.py +@@ -43,6 +43,7 @@ def configuration(parent_package='', top_path=None): + sources=['lapack_litemodule.c', get_lapack_lite_sources], + depends=['lapack_lite/f2c.h'], + extra_info=lapack_info, ++ libraries=['m'], + ) ---- a/numpy/random/setup.orig.py 2015-07-09 14:44:41.105174826 +0200 -+++ b/numpy/random/setup.py 2015-07-09 14:46:08.592679877 +0200 -@@ -38,7 +38,7 @@ + # umath_linalg module +@@ -51,7 +52,7 @@ def configuration(parent_package='', top_path=None): + sources=['umath_linalg.c.src', get_lapack_lite_sources], + depends=['lapack_lite/f2c.h'], + extra_info=lapack_info, +- libraries=['npymath'], ++ libraries=['npymath', 'm'], + ) + return config + +diff --git a/numpy/random/setup.py b/numpy/random/setup.py +index 3f3b773a4..c1db9f783 100644 +--- a/numpy/random/setup.py ++++ b/numpy/random/setup.py +@@ -40,7 +40,7 @@ def configuration(parent_package='',top_path=None): if needs_mingw_ftime_workaround(): defs.append(("NPY_NEEDS_MINGW_TIME_WORKAROUND", None)) diff --git a/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch b/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch index 73f2a927..8ff3775c 100644 --- a/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch +++ b/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch @@ -1,8 +1,8 @@ diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py -index a050430..471e958 100644 +index bea120cf9..a448a83fc 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py -@@ -610,6 +610,7 @@ class system_info: +@@ -719,6 +719,7 @@ class system_info(object): return self.get_paths(self.section, key) def get_libs(self, key, default): diff --git a/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch b/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch new file mode 100644 index 00000000..59d225df --- /dev/null +++ b/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch @@ -0,0 +1,69 @@ +diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c +index c70f852..695efd5 100644 +--- a/numpy/core/src/multiarray/common.c ++++ b/numpy/core/src/multiarray/common.c +@@ -852,3 +852,12 @@ _may_have_objects(PyArray_Descr *dtype) + return (PyDataType_HASFIELDS(base) || + PyDataType_FLAGCHK(base, NPY_ITEM_HASOBJECT) ); + } ++ ++/* ++ * Dummy to fix android NDK problem with missing reference. ++ */ ++void * ++__emutls_get_address(struct __emutls_object *obj) ++{ ++ return NULL; ++} +diff --git a/numpy/distutils/exec_command.py b/numpy/distutils/exec_command.py +index 8118e2f..b586442 100644 +--- a/numpy/distutils/exec_command.py ++++ b/numpy/distutils/exec_command.py +@@ -260,7 +260,7 @@ def _exec_command(command, use_shell=None, use_tee = None, **env): + return 127, '' + + text, err = proc.communicate() +- text = text.decode(locale.getpreferredencoding(False), ++ text = text.decode('UTF-8', + errors='replace') + + text = text.replace('\r\n', '\n') +diff --git a/numpy/distutils/misc_util.py b/numpy/distutils/misc_util.py +index f2d677a..758b1ed 100644 +--- a/numpy/distutils/misc_util.py ++++ b/numpy/distutils/misc_util.py +@@ -9,7 +9,6 @@ import atexit + import tempfile + import subprocess + import shutil +-import multiprocessing + + import distutils + from distutils.errors import DistutilsError +@@ -93,10 +92,7 @@ def get_num_build_jobs(): + + """ + from numpy.distutils.core import get_distribution +- try: +- cpu_count = len(os.sched_getaffinity(0)) +- except AttributeError: +- cpu_count = multiprocessing.cpu_count() ++ cpu_count = 1 + envjobs = int(os.environ.get("NPY_NUM_BUILD_JOBS", cpu_count)) + dist = get_distribution() + # may be None during configuration +diff --git a/setup.py b/setup.py +index fed178e..b0266eb 100755 +--- a/setup.py ++++ b/setup.py +@@ -377,9 +377,8 @@ def setup_package(): + # Raise errors for unsupported commands, improve help output, etc. + run_build = parse_setuppy_commands() + +- from setuptools import setup ++ from numpy.distutils.core import setup + if run_build: +- from numpy.distutils.core import setup + cwd = os.path.abspath(os.path.dirname(__file__)) + if not os.path.exists(os.path.join(cwd, 'PKG-INFO')): + # Generate Cython sources, unless building from source release diff --git a/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py b/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py new file mode 100644 index 00000000..c87034ce --- /dev/null +++ b/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py @@ -0,0 +1,22 @@ +from pythonforandroid.recipe import PythonRecipe + + +class OmemoBackendSignalRecipe(PythonRecipe): + name = 'omemo-backend-signal' + version = '0.2.2' + url = 'https://pypi.python.org/packages/source/o/omemo-backend-signal/omemo-backend-signal-{version}.tar.gz' + site_packages_name = 'omemo-backend-signal' + depends = [ + 'setuptools', + 'protobuf_cpp', + 'x3dh', + 'DoubleRatchet', + 'hkdf==0.0.3', + 'cryptography', + 'omemo', + ] + patches = ['wireformat.patch'] + call_hostpython_via_targetpython = False + + +recipe = OmemoBackendSignalRecipe() diff --git a/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch b/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch new file mode 100644 index 00000000..7881d05e --- /dev/null +++ b/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch @@ -0,0 +1,101 @@ +diff -urN omemo-backend-signal-0.2.2.ori/omemo_backend_signal/whispertextprotocol_pb2.py omemo-backend-signal-0.2.2/omemo_backend_signal/whispertextprotocol_pb2.py +--- omemo-backend-signal-0.2.2.ori/omemo_backend_signal/whispertextprotocol_pb2.py 2018-09-02 21:04:26.000000000 +0200 ++++ omemo-backend-signal-0.2.2/omemo_backend_signal/whispertextprotocol_pb2.py 2018-11-02 10:39:15.196715321 +0100 +@@ -21,7 +21,6 @@ + syntax='proto2', + serialized_pb=_b('\n\x19WhisperTextProtocol.proto\"R\n\rSignalMessage\x12\x16\n\x0e\x64h_ratchet_key\x18\x01 \x01(\x0c\x12\t\n\x01n\x18\x02 \x01(\r\x12\n\n\x02pn\x18\x03 \x01(\r\x12\x12\n\nciphertext\x18\x04 \x01(\x0c\"\x7f\n\x13PreKeySignalMessage\x12\x17\n\x0fregistration_id\x18\x05 \x01(\r\x12\x0f\n\x07otpk_id\x18\x01 \x01(\r\x12\x0e\n\x06spk_id\x18\x06 \x01(\r\x12\n\n\x02\x65k\x18\x02 \x01(\x0c\x12\n\n\x02ik\x18\x03 \x01(\x0c\x12\x16\n\x0esignal_message\x18\x04 \x01(\x0c') + ) +-_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + +@@ -39,28 +38,28 @@ + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='n', full_name='SignalMessage.n', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='pn', full_name='SignalMessage.pn', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ciphertext', full_name='SignalMessage.ciphertext', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + ], + extensions=[ + ], +@@ -91,42 +90,42 @@ + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='otpk_id', full_name='PreKeySignalMessage.otpk_id', index=1, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='spk_id', full_name='PreKeySignalMessage.spk_id', index=2, + number=6, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ek', full_name='PreKeySignalMessage.ek', index=3, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ik', full_name='PreKeySignalMessage.ik', index=4, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='signal_message', full_name='PreKeySignalMessage.signal_message', index=5, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, +- options=None), ++ options=None, file=DESCRIPTOR), + ], + extensions=[ + ], +@@ -145,6 +144,7 @@ + + DESCRIPTOR.message_types_by_name['SignalMessage'] = _SIGNALMESSAGE + DESCRIPTOR.message_types_by_name['PreKeySignalMessage'] = _PREKEYSIGNALMESSAGE ++_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + SignalMessage = _reflection.GeneratedProtocolMessageType('SignalMessage', (_message.Message,), dict( + DESCRIPTOR = _SIGNALMESSAGE, diff --git a/p4a/pythonforandroid/recipes/omemo/__init__.py b/p4a/pythonforandroid/recipes/omemo/__init__.py new file mode 100644 index 00000000..a940105a --- /dev/null +++ b/p4a/pythonforandroid/recipes/omemo/__init__.py @@ -0,0 +1,17 @@ +from pythonforandroid.recipe import PythonRecipe + + +class OmemoRecipe(PythonRecipe): + name = 'omemo' + version = '0.10.3' + url = 'https://pypi.python.org/packages/source/O/OMEMO/OMEMO-{version}.tar.gz' + site_packages_name = 'omemo' + depends = [ + 'setuptools', + 'x3dh', + 'cryptography', + ] + call_hostpython_via_targetpython = False + + +recipe = OmemoRecipe() diff --git a/p4a/pythonforandroid/recipes/openal/__init__.py b/p4a/pythonforandroid/recipes/openal/__init__.py new file mode 100644 index 00000000..ad93065f --- /dev/null +++ b/p4a/pythonforandroid/recipes/openal/__init__.py @@ -0,0 +1,39 @@ +from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.toolchain import current_directory, shprint +from os.path import join +import os +import sh + + +class OpenALRecipe(NDKRecipe): + version = '1.18.2' + url = 'https://github.com/kcat/openal-soft/archive/openal-soft-{version}.tar.gz' + + generated_libraries = ['libopenal.so'] + + def prebuild_arch(self, arch): + # we need to build native tools for host system architecture + with current_directory(join(self.get_build_dir(arch.arch), 'native-tools')): + shprint(sh.cmake, '.', _env=os.environ) + shprint(sh.make, _env=os.environ) + + def build_arch(self, arch): + with current_directory(self.get_build_dir(arch.arch)): + env = self.get_recipe_env(arch) + cmake_args = [ + '-DCMAKE_TOOLCHAIN_FILE={}'.format('XCompile-Android.txt'), + '-DHOST={}'.format(self.ctx.toolchain_prefix) + ] + if self.ctx.ndk == 'crystax': + # avoids a segfault in libcrystax when calling lrintf + cmake_args += ['-DHAVE_LRINTF=0'] + shprint( + sh.cmake, '.', + *cmake_args, + _env=env + ) + shprint(sh.make, _env=env) + self.install_libs(arch, 'libopenal.so') + + +recipe = OpenALRecipe() diff --git a/p4a/pythonforandroid/recipes/opencv/__init__.py b/p4a/pythonforandroid/recipes/opencv/__init__.py index 7e70162e..6932bc22 100644 --- a/p4a/pythonforandroid/recipes/opencv/__init__.py +++ b/p4a/pythonforandroid/recipes/opencv/__init__.py @@ -1,53 +1,135 @@ -import os +from os.path import join import sh +from pythonforandroid.recipe import NDKRecipe from pythonforandroid.toolchain import ( - NDKRecipe, - Recipe, current_directory, - info, shprint, ) from multiprocessing import cpu_count class OpenCVRecipe(NDKRecipe): - version = '2.4.10.1' - url = 'https://github.com/Itseez/opencv/archive/{version}.zip' - #md5sum = '2ddfa98e867e6611254040df841186dc' + ''' + .. versionchanged:: 0.7.1 + rewrote recipe to support the python bindings (cv2.so) and enable the + build of most of the libraries of the opencv's package, so we can + process images, videos, objects, photos... + ''' + version = '4.0.1' + url = 'https://github.com/opencv/opencv/archive/{version}.zip' depends = ['numpy'] - patches = ['patches/p4a_build-2.4.10.1.patch'] - generated_libraries = ['cv2.so'] - - def prebuild_arch(self, arch): - self.apply_patches(arch) - - def get_recipe_env(self,arch): + patches = ['patches/p4a_build.patch'] + generated_libraries = [ + 'libopencv_features2d.so', + 'libopencv_imgproc.so', + 'libopencv_stitching.so', + 'libopencv_calib3d.so', + 'libopencv_flann.so', + 'libopencv_ml.so', + 'libopencv_videoio.so', + 'libopencv_core.so', + 'libopencv_highgui.so', + 'libopencv_objdetect.so', + 'libopencv_video.so', + 'libopencv_dnn.so', + 'libopencv_imgcodecs.so', + 'libopencv_photo.so' + ] + + def get_lib_dir(self, arch): + return join(self.get_build_dir(arch.arch), 'build', 'lib', arch.arch) + + def get_recipe_env(self, arch): env = super(OpenCVRecipe, self).get_recipe_env(arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() env['ANDROID_NDK'] = self.ctx.ndk_dir env['ANDROID_SDK'] = self.ctx.sdk_dir - env['SITEPACKAGES_PATH'] = self.ctx.get_site_packages_dir() return env def build_arch(self, arch): - with current_directory(self.get_build_dir(arch.arch)): + build_dir = join(self.get_build_dir(arch.arch), 'build') + shprint(sh.mkdir, '-p', build_dir) + with current_directory(build_dir): env = self.get_recipe_env(arch) - cvsrc = self.get_build_dir(arch.arch) - lib_dir = os.path.join(self.ctx.get_python_install_dir(), "lib") - + + python_major = self.ctx.python_recipe.version[0] + python_include_root = self.ctx.python_recipe.include_root(arch.arch) + python_site_packages = self.ctx.get_site_packages_dir() + python_link_root = self.ctx.python_recipe.link_root(arch.arch) + python_link_version = self.ctx.python_recipe.major_minor_version_string + if 'python3' in self.ctx.python_recipe.name: + python_link_version += 'm' + python_library = join(python_link_root, + 'libpython{}.so'.format(python_link_version)) + python_include_numpy = join(python_site_packages, + 'numpy', 'core', 'include') + shprint(sh.cmake, - '-DP4A=ON','-DANDROID_ABI={}'.format(arch.arch), - '-DCMAKE_TOOLCHAIN_FILE={}/platforms/android/android.toolchain.cmake'.format(cvsrc), - '-DPYTHON_INCLUDE_PATH={}/include/python2.7'.format(env['PYTHON_ROOT']), - '-DPYTHON_LIBRARY={}/lib/libpython2.7.so'.format(env['PYTHON_ROOT']), - '-DPYTHON_NUMPY_INCLUDE_DIR={}/numpy/core/include'.format(env['SITEPACKAGES_PATH']), - '-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']), - '-DBUILD_TESTS=OFF', '-DBUILD_PERF_TESTS=OFF', '-DBUILD_EXAMPLES=OFF', '-DBUILD_ANDROID_EXAMPLES=OFF', - '-DPYTHON_PACKAGES_PATH={}'.format(env['SITEPACKAGES_PATH']), - cvsrc, - _env=env) - shprint(sh.make,'-j',str(cpu_count()),'opencv_python') - shprint(sh.cmake,'-DCOMPONENT=python','-P','./cmake_install.cmake') - sh.cp('-a',sh.glob('./lib/{}/lib*.so'.format(arch.arch)),lib_dir) + '-DP4A=ON', + '-DANDROID_ABI={}'.format(arch.arch), + '-DANDROID_STANDALONE_TOOLCHAIN={}'.format(self.ctx.ndk_dir), + '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api), + '-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']), + + '-DCMAKE_TOOLCHAIN_FILE={}'.format( + join(self.ctx.ndk_dir, 'build', 'cmake', + 'android.toolchain.cmake')), + # Make the linkage with our python library, otherwise we + # will get dlopen error when trying to import cv2's module. + '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lpython{version}'.format( + path=python_link_root, + version=python_link_version), + + '-DBUILD_WITH_STANDALONE_TOOLCHAIN=ON', + # Force to build as shared libraries the cv2's dependant + # libs or we will not be able to link with our python + '-DBUILD_SHARED_LIBS=ON', + '-DBUILD_STATIC_LIBS=OFF', + + # Disable some opencv's features + '-DBUILD_opencv_java=OFF', + '-DBUILD_opencv_java_bindings_generator=OFF', + # '-DBUILD_opencv_highgui=OFF', + # '-DBUILD_opencv_imgproc=OFF', + # '-DBUILD_opencv_flann=OFF', + '-DBUILD_TESTS=OFF', + '-DBUILD_PERF_TESTS=OFF', + '-DENABLE_TESTING=OFF', + '-DBUILD_EXAMPLES=OFF', + '-DBUILD_ANDROID_EXAMPLES=OFF', + + # Force to only build our version of python + '-DBUILD_OPENCV_PYTHON{major}=ON'.format(major=python_major), + '-DBUILD_OPENCV_PYTHON{major}=OFF'.format( + major='2' if python_major == '3' else '3'), + + # Force to install the `cv2.so` library directly into + # python's site packages (otherwise the cv2's loader fails + # on finding the cv2.so library) + '-DOPENCV_SKIP_PYTHON_LOADER=ON', + '-DOPENCV_PYTHON{major}_INSTALL_PATH={site_packages}'.format( + major=python_major, site_packages=python_site_packages), + + # Define python's paths for: exe, lib, includes, numpy... + '-DPYTHON_DEFAULT_EXECUTABLE={}'.format(self.ctx.hostpython), + '-DPYTHON{major}_EXECUTABLE={host_python}'.format( + major=python_major, host_python=self.ctx.hostpython), + '-DPYTHON{major}_INCLUDE_PATH={include_path}'.format( + major=python_major, include_path=python_include_root), + '-DPYTHON{major}_LIBRARIES={python_lib}'.format( + major=python_major, python_lib=python_library), + '-DPYTHON{major}_NUMPY_INCLUDE_DIRS={numpy_include}'.format( + major=python_major, numpy_include=python_include_numpy), + '-DPYTHON{major}_PACKAGES_PATH={site_packages}'.format( + major=python_major, site_packages=python_site_packages), + + self.get_build_dir(arch.arch), + _env=env) + shprint(sh.make, '-j' + str(cpu_count()), 'opencv_python' + python_major) + # Install python bindings (cv2.so) + shprint(sh.cmake, '-DCOMPONENT=python', '-P', './cmake_install.cmake') + # Copy third party shared libs that we need in our final apk + sh.cp('-a', sh.glob('./lib/{}/lib*.so'.format(arch.arch)), + self.ctx.get_libs_dir(arch.arch)) + recipe = OpenCVRecipe() diff --git a/p4a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch b/p4a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch deleted file mode 100644 index a7a60aa3..00000000 --- a/p4a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch +++ /dev/null @@ -1,66 +0,0 @@ -diff --git a/cmake/OpenCVDetectPython.cmake b/cmake/OpenCVDetectPython.cmake -index 31c2c1e..c890917 100644 ---- a/cmake/OpenCVDetectPython.cmake -+++ b/cmake/OpenCVDetectPython.cmake -@@ -36,7 +36,7 @@ if(PYTHON_EXECUTABLE) - unset(PYTHON_VERSION_FULL) - endif() - -- if(NOT ANDROID AND NOT IOS) -+ if(P4A OR NOT ANDROID AND NOT IOS) - ocv_check_environment_variables(PYTHON_LIBRARY PYTHON_INCLUDE_DIR) - if(CMAKE_CROSSCOMPILING) - find_host_package(PythonLibs ${PYTHON_VERSION_MAJOR_MINOR}) -@@ -51,7 +51,7 @@ if(PYTHON_EXECUTABLE) - endif() - endif() - -- if(NOT ANDROID AND NOT IOS) -+ if(P4A OR NOT ANDROID AND NOT IOS) - if(CMAKE_HOST_UNIX) - execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import *; print get_python_lib()" - RESULT_VARIABLE PYTHON_CVPY_PROCESS -@@ -117,7 +117,7 @@ if(PYTHON_EXECUTABLE) - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() - endif() -- endif(NOT ANDROID AND NOT IOS) -+ endif(P4A OR NOT ANDROID AND NOT IOS) - - if(BUILD_DOCS) - find_host_program(SPHINX_BUILD sphinx-build) -diff --git a/modules/python/CMakeLists.txt b/modules/python/CMakeLists.txt -index 3c0f2fd..7ba234a 100644 ---- a/modules/python/CMakeLists.txt -+++ b/modules/python/CMakeLists.txt -@@ -5,7 +5,7 @@ - if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug") - ocv_module_disable(python) - endif() --if(ANDROID OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY) -+if(ANDROID AND NOT P4A OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY) - ocv_module_disable(python) - endif() - -diff --git a/modules/androidcamera/src/camera_activity.cpp b/modules/androidcamera/src/camera_activity.cpp -index 84db3e1..4222526 100644 ---- a/modules/androidcamera/src/camera_activity.cpp -+++ b/modules/androidcamera/src/camera_activity.cpp -@@ -7,6 +7,7 @@ - #include - #include - #include -+#include - #include - #include "camera_activity.hpp" - #include "camera_wrapper.h" -@@ -342,6 +343,8 @@ std::string CameraWrapperConnector::getPathLibFolder() - - char* pathEnd = strrchr(pathBegin, '/'); - pathEnd[1] = 0; -+ pathBegin = realpath((std::string(pathBegin)+"../../../../lib").c_str(), lineBuf); -+ pathBegin = strcat(pathBegin, "/"); - - LOGD("Libraries folder found: %s", pathBegin); - - diff --git a/p4a/pythonforandroid/recipes/opencv/patches/p4a_build.patch b/p4a/pythonforandroid/recipes/opencv/patches/p4a_build.patch new file mode 100644 index 00000000..fd60c01d --- /dev/null +++ b/p4a/pythonforandroid/recipes/opencv/patches/p4a_build.patch @@ -0,0 +1,33 @@ +This patch allow that the opencv's build command correctly detects our version +of python, so we can successfully build the python bindings (cv2.so) +--- opencv-4.0.1/cmake/OpenCVDetectPython.cmake.orig 2018-12-22 08:03:30.000000000 +0100 ++++ opencv-4.0.1/cmake/OpenCVDetectPython.cmake 2019-01-31 11:33:10.896502978 +0100 +@@ -175,7 +175,7 @@ if(NOT ${found}) + endif() + endif() + +- if(NOT ANDROID AND NOT IOS) ++ if(P4A OR NOT ANDROID AND NOT IOS) + if(CMAKE_HOST_UNIX) + execute_process(COMMAND ${_executable} -c "from distutils.sysconfig import *; print(get_python_lib())" + RESULT_VARIABLE _cvpy_process +@@ -244,7 +244,7 @@ if(NOT ${found}) + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + endif() +- endif(NOT ANDROID AND NOT IOS) ++ endif(P4A OR NOT ANDROID AND NOT IOS) + endif() + + # Export return values +--- opencv-4.0.1/modules/python/CMakeLists.txt.orig 2018-12-22 08:03:30.000000000 +0100 ++++ opencv-4.0.1/modules/python/CMakeLists.txt 2019-01-31 11:47:17.100494908 +0100 +@@ -3,7 +3,7 @@ + # ---------------------------------------------------------------------------- + if(DEFINED OPENCV_INITIAL_PASS) # OpenCV build + +-if(ANDROID OR APPLE_FRAMEWORK OR WINRT) ++if(ANDROID AND NOT P4A OR APPLE_FRAMEWORK OR WINRT) + ocv_module_disable_(python2) + ocv_module_disable_(python3) + return() diff --git a/p4a/pythonforandroid/recipes/openssl/__init__.py b/p4a/pythonforandroid/recipes/openssl/__init__.py index 5be1cdd4..3a9505f4 100644 --- a/p4a/pythonforandroid/recipes/openssl/__init__.py +++ b/p4a/pythonforandroid/recipes/openssl/__init__.py @@ -1,41 +1,141 @@ -from functools import partial +from os.path import join from pythonforandroid.toolchain import Recipe, shprint, current_directory import sh class OpenSSLRecipe(Recipe): - version = '1.0.2h' - url = 'https://www.openssl.org/source/openssl-{version}.tar.gz' + ''' + The OpenSSL libraries for python-for-android. This recipe will generate the + following libraries as shared libraries (*.so): + + - crypto + - ssl + + The generated openssl libraries are versioned, where the version is the + recipe attribute :attr:`version` e.g.: ``libcrypto1.1.so``, + ``libssl1.1.so``...so...to link your recipe with the openssl libs, + remember to add the version at the end, e.g.: + ``-lcrypto1.1 -lssl1.1``. Or better, you could do it dynamically + using the methods: :meth:`include_flags`, :meth:`link_dirs_flags` and + :meth:`link_libs_flags`. + + .. note:: the python2legacy version is too old to support openssl 1.1+, so + we must use version 1.0.x. Also python3crystax is not building + successfully with openssl libs 1.1+ so we use the legacy version as + we do with python2legacy. + + .. warning:: This recipe is very sensitive because is used for our core + recipes, the python recipes. The used API should match with the one + used in our python build, otherwise we will be unable to build the + _ssl.so python module. + + .. versionchanged:: 0.6.0 + + - The gcc compiler has been deprecated in favour of clang and libraries + updated to version 1.1.1 (LTS - supported until 11th September 2023) + - Added two new methods to make easier to link with openssl: + :meth:`include_flags` and :meth:`link_flags` + - subclassed versioned_url + - Adapted method :meth:`select_build_arch` to API 21+ + - Add ability to build a legacy version of the openssl libs when using + python2legacy or python3crystax. + + ''' + + standard_version = '1.1' + '''the major minor version used to link our recipes''' + legacy_version = '1.0' + '''the major minor version used to link our recipes when using + python2legacy or python3crystax''' + + standard_url_version = '1.1.1' + '''the version used to download our libraries''' + legacy_url_version = '1.0.2q' + '''the version used to download our libraries when using python2legacy or + python3crystax''' + + url = 'https://www.openssl.org/source/openssl-{url_version}.tar.gz' + + @property + def use_legacy(self): + if not self.ctx.recipe_build_order: + return False + return any([i for i in ('python2legacy', 'python3crystax') if + i in self.ctx.recipe_build_order]) + + @property + def version(self): + if self.use_legacy: + return self.legacy_version + return self.standard_version + + @property + def url_version(self): + if self.use_legacy: + return self.legacy_url_version + return self.standard_url_version + + @property + def versioned_url(self): + if self.url is None: + return None + return self.url.format(url_version=self.url_version) + + def get_build_dir(self, arch): + return join(self.get_build_container_dir(arch), self.name + self.version) + + def include_flags(self, arch): + '''Returns a string with the include folders''' + openssl_includes = join(self.get_build_dir(arch.arch), 'include') + return (' -I' + openssl_includes + + ' -I' + join(openssl_includes, 'internal') + + ' -I' + join(openssl_includes, 'openssl')) + + def link_dirs_flags(self, arch): + '''Returns a string with the appropriate `-L` to link + with the openssl libs. This string is usually added to the environment + variable `LDFLAGS`''' + return ' -L' + self.get_build_dir(arch.arch) + + def link_libs_flags(self): + '''Returns a string with the appropriate `-l` flags to link with + the openssl libs. This string is usually added to the environment + variable `LIBS`''' + return ' -lcrypto{version} -lssl{version}'.format(version=self.version) + + def link_flags(self, arch): + '''Returns a string with the flags to link with the openssl libraries + in the format: `-L -l`''' + return self.link_dirs_flags(arch) + self.link_libs_flags() def should_build(self, arch): return not self.has_libs(arch, 'libssl' + self.version + '.so', 'libcrypto' + self.version + '.so') - def check_symbol(self, env, sofile, symbol): - nm = env.get('NM', 'nm') - syms = sh.sh('-c', "{} -gp {} | cut -d' ' -f3".format( - nm, sofile), _env=env).splitlines() - if symbol in syms: - return True - print('{} missing symbol {}; rebuilding'.format(sofile, symbol)) - return False - def get_recipe_env(self, arch=None): - env = super(OpenSSLRecipe, self).get_recipe_env(arch) + env = super(OpenSSLRecipe, self).get_recipe_env(arch, clang=not self.use_legacy) env['OPENSSL_VERSION'] = self.version - env['CFLAGS'] += ' ' + env['LDFLAGS'] - env['CC'] += ' ' + env['LDFLAGS'] + env['MAKE'] = 'make' # This removes the '-j5', which isn't safe + if self.use_legacy: + env['CFLAGS'] += ' ' + env['LDFLAGS'] + env['CC'] += ' ' + env['LDFLAGS'] + else: + env['ANDROID_NDK'] = self.ctx.ndk_dir return env def select_build_arch(self, arch): aname = arch.arch if 'arm64' in aname: - return 'linux-aarch64' + return 'android-arm64' if not self.use_legacy else 'linux-aarch64' if 'v7a' in aname: - return 'android-armv7' + return 'android-arm' if not self.use_legacy else 'android-armv7' if 'arm' in aname: return 'android' + if 'x86_64' in aname: + return 'android-x86_64' + if 'x86' in aname: + return 'android-x86' return 'linux-armv4' def build_arch(self, arch): @@ -45,19 +145,29 @@ class OpenSSLRecipe(Recipe): # so instead we manually run perl passing in Configure perl = sh.Command('perl') buildarch = self.select_build_arch(arch) - shprint(perl, 'Configure', 'shared', 'no-dso', 'no-krb5', buildarch, _env=env) - self.apply_patch('disable-sover.patch', arch.arch) - self.apply_patch('rename-shared-lib.patch', arch.arch) + # XXX if we don't have no-asm, using clang and ndk-15c, i got: + # crypto/aes/bsaes-armv7.S:1372:14: error: immediate operand must be in the range [0,4095] + # add r8, r6, #.LREVM0SR-.LM0 @ borrow r8 + # ^ + # crypto/aes/bsaes-armv7.S:1434:14: error: immediate operand must be in the range [0,4095] + # sub r6, r8, #.LREVM0SR-.LSR @ pass constants + config_args = ['shared', 'no-dso', 'no-asm'] + if self.use_legacy: + config_args.append('no-krb5') + config_args.append(buildarch) + if not self.use_legacy: + config_args.append('-D__ANDROID_API__={}'.format(self.ctx.ndk_api)) + shprint(perl, 'Configure', *config_args, _env=env) + self.apply_patch( + 'disable-sover{}.patch'.format( + '-legacy' if self.use_legacy else ''), arch.arch) + if self.use_legacy: + self.apply_patch('rename-shared-lib.patch', arch.arch) - # check_ssl = partial(self.check_symbol, env, 'libssl' + self.version + '.so') - check_crypto = partial(self.check_symbol, env, 'libcrypto' + self.version + '.so') - while True: - shprint(sh.make, 'build_libs', _env=env) - if all(map(check_crypto, ('SSLeay', 'MD5_Transform', 'MD4_Init'))): - break - shprint(sh.make, 'clean', _env=env) + shprint(sh.make, 'build_libs', _env=env) self.install_libs(arch, 'libssl' + self.version + '.so', 'libcrypto' + self.version + '.so') + recipe = OpenSSLRecipe() diff --git a/p4a/pythonforandroid/recipes/openssl/disable-sover-legacy.patch b/p4a/pythonforandroid/recipes/openssl/disable-sover-legacy.patch new file mode 100644 index 00000000..6099fadc --- /dev/null +++ b/p4a/pythonforandroid/recipes/openssl/disable-sover-legacy.patch @@ -0,0 +1,20 @@ +--- openssl/Makefile 2016-01-28 17:26:49.159522273 +0100 ++++ b/Makefile 2016-01-28 17:26:54.358438402 +0100 +@@ -342,7 +342,7 @@ + link-shared: + @ set -e; for i in $(SHLIBDIRS); do \ + $(MAKE) -f $(HERE)/Makefile.shared -e $(BUILDENV) \ +- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \ ++ LIBNAME=$$i LIBVERSION= \ + LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \ + symlink.$(SHLIB_TARGET); \ + libs="$$libs -l$$i"; \ +@@ -356,7 +356,7 @@ + libs="$(LIBKRB5) $$libs"; \ + fi; \ + $(CLEARENV) && $(MAKE) -f Makefile.shared -e $(BUILDENV) \ +- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \ ++ LIBNAME=$$i LIBVERSION= \ + LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \ + LIBDEPS="$$libs $(EX_LIBS)" \ + link_a.$(SHLIB_TARGET); \ diff --git a/p4a/pythonforandroid/recipes/openssl/disable-sover.patch b/p4a/pythonforandroid/recipes/openssl/disable-sover.patch index 6099fadc..d944483c 100644 --- a/p4a/pythonforandroid/recipes/openssl/disable-sover.patch +++ b/p4a/pythonforandroid/recipes/openssl/disable-sover.patch @@ -1,20 +1,11 @@ ---- openssl/Makefile 2016-01-28 17:26:49.159522273 +0100 -+++ b/Makefile 2016-01-28 17:26:54.358438402 +0100 -@@ -342,7 +342,7 @@ - link-shared: - @ set -e; for i in $(SHLIBDIRS); do \ - $(MAKE) -f $(HERE)/Makefile.shared -e $(BUILDENV) \ -- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \ -+ LIBNAME=$$i LIBVERSION= \ - LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \ - symlink.$(SHLIB_TARGET); \ - libs="$$libs -l$$i"; \ -@@ -356,7 +356,7 @@ - libs="$(LIBKRB5) $$libs"; \ - fi; \ - $(CLEARENV) && $(MAKE) -f Makefile.shared -e $(BUILDENV) \ -- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \ -+ LIBNAME=$$i LIBVERSION= \ - LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \ - LIBDEPS="$$libs $(EX_LIBS)" \ - link_a.$(SHLIB_TARGET); \ +--- openssl/Makefile.orig 2018-10-20 22:49:40.418310423 +0200 ++++ openssl/Makefile 2018-10-20 22:50:23.347322403 +0200 +@@ -19,7 +19,7 @@ + SHLIB_MAJOR=1 + SHLIB_MINOR=1 + SHLIB_TARGET=linux-shared +-SHLIB_EXT=.so.$(SHLIB_VERSION_NUMBER) ++SHLIB_EXT=$(SHLIB_VERSION_NUMBER).so + SHLIB_EXT_SIMPLE=.so + SHLIB_EXT_IMPORT= + diff --git a/p4a/pythonforandroid/recipes/pil/__init__.py b/p4a/pythonforandroid/recipes/pil/__init__.py index 3f79bace..f3ad2f42 100644 --- a/p4a/pythonforandroid/recipes/pil/__init__.py +++ b/p4a/pythonforandroid/recipes/pil/__init__.py @@ -1,41 +1,79 @@ -from os.path import join - +from os.path import join, exists from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from pythonforandroid.toolchain import shprint +import sh class PILRecipe(CompiledComponentsPythonRecipe): - name = 'pil' - version = '1.1.7' - url = 'http://effbot.org/downloads/Imaging-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'png', 'jpeg'] - site_packages_name = 'PIL' + name = 'pil' + version = '1.1.7' + url = 'http://effbot.org/downloads/Imaging-{version}.tar.gz' + depends = ['png', 'jpeg', 'setuptools'] + opt_depends = ['freetype'] + site_packages_name = 'PIL' - patches = ['disable-tk.patch', - 'fix-directories.patch'] + patches = ['disable-tk.patch', + 'fix-directories.patch'] - def get_recipe_env(self, arch=None): - env = super(PILRecipe, self).get_recipe_env(arch) + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + env = super(PILRecipe, self).get_recipe_env(arch, with_flags_in_cc) - png = self.get_recipe('png', self.ctx) - png_lib_dir = png.get_lib_dir(arch) - png_jni_dir = png.get_jni_dir(arch) - jpeg = self.get_recipe('jpeg', self.ctx) - jpeg_lib_dir = jpeg.get_lib_dir(arch) - jpeg_jni_dir = jpeg.get_jni_dir(arch) - env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_jni_dir) + env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch) + env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch) - cflags = ' -I{} -L{} -I{} -L{}'.format(png_jni_dir, png_lib_dir, jpeg_jni_dir, jpeg_lib_dir) - env['CFLAGS'] += cflags - env['CXXFLAGS'] += cflags - env['CC'] += cflags - env['CXX'] += cflags + ndk_lib_dir = join(self.ctx.ndk_platform, 'usr', 'lib') + ndk_include_dir = join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include') - ndk_dir = self.ctx.ndk_platform - ndk_lib_dir = join(ndk_dir, 'usr', 'lib') - ndk_include_dir = join(ndk_dir, 'usr', 'include') - env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir) + png = self.get_recipe('png', self.ctx) + png_lib_dir = png.get_lib_dir(arch) + png_jni_dir = png.get_jni_dir(arch) - return env + jpeg = self.get_recipe('jpeg', self.ctx) + jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch) + + if 'freetype' in self.ctx.recipe_build_order: + freetype = self.get_recipe('freetype', self.ctx) + free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs') + free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include') + # hack freetype to be found by pil + freetype_link = join(free_inc_dir, 'freetype') + if not exists(freetype_link): + shprint(sh.ln, '-s', join(free_inc_dir), freetype_link) + + harfbuzz = self.get_recipe('harfbuzz', self.ctx) + harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs') + harf_inc_dir = harfbuzz.get_build_dir(arch.arch) + + env['FREETYPE_ROOT'] = '{}|{}'.format(free_lib_dir, free_inc_dir) + + env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_inc_dir) + env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir) + + cflags = ' -std=c99' + cflags += ' -I{}'.format(png_jni_dir) + if 'freetype' in self.ctx.recipe_build_order: + cflags += ' -I{} -I{}'.format(harf_inc_dir, join(harf_inc_dir, 'src')) + cflags += ' -I{}'.format(free_inc_dir) + cflags += ' -I{}'.format(jpeg_inc_dir) + cflags += ' -I{}'.format(ndk_include_dir) + + py_v = self.ctx.python_recipe.major_minor_version_string + if py_v[0] == '3': + py_v += 'm' + + env['LIBS'] = ' -lpython{version} -lpng'.format(version=py_v) + if 'freetype' in self.ctx.recipe_build_order: + env['LIBS'] += ' -lfreetype -lharfbuzz' + env['LIBS'] += ' -ljpeg -lturbojpeg' + + env['LDFLAGS'] += ' -L{} -L{}'.format(env['PYTHON_LINK_ROOT'], png_lib_dir) + if 'freetype' in self.ctx.recipe_build_order: + env['LDFLAGS'] += ' -L{} -L{}'.format(harf_lib_dir, free_lib_dir) + env['LDFLAGS'] += ' -L{} -L{}'.format(jpeg_lib_dir, ndk_lib_dir) + + if cflags not in env['CFLAGS']: + env['CFLAGS'] += cflags + return env recipe = PILRecipe() diff --git a/p4a/pythonforandroid/recipes/pil/fix-directories.patch b/p4a/pythonforandroid/recipes/pil/fix-directories.patch index d54cd0cb..b9daee3a 100644 --- a/p4a/pythonforandroid/recipes/pil/fix-directories.patch +++ b/p4a/pythonforandroid/recipes/pil/fix-directories.patch @@ -1,7 +1,6 @@ -diff -Naur pil/setup.py b/setup.py ---- pil/setup.py 2015-12-11 16:42:40.817701332 -0600 -+++ b/setup.py 2015-12-11 17:07:34.778477132 -0600 -@@ -34,10 +34,10 @@ +--- pil/setup.py.orig 2009-11-15 17:06:10.000000000 +0100 ++++ pil/setup.py 2019-01-04 11:08:47.302974315 +0100 +@@ -34,10 +34,10 @@ def libinclude(root): # TIFF_ROOT = libinclude("/opt/tiff") TCL_ROOT = None @@ -15,7 +14,7 @@ diff -Naur pil/setup.py b/setup.py LCMS_ROOT = None # FIXME: add mechanism to explicitly *disable* the use of a library -@@ -127,29 +127,6 @@ +@@ -127,33 +127,10 @@ class pil_build_ext(build_ext): add_directory(include_dirs, "libImaging") @@ -42,10 +41,17 @@ diff -Naur pil/setup.py b/setup.py - add_directory(library_dirs, "/usr/local/lib") - # FIXME: check /opt/stuff directories here? - - prefix = sysconfig.get_config_var("prefix") +- prefix = sysconfig.get_config_var("prefix") ++ prefix = os.environ.get('PYTHON_LINK_ROOT') if prefix: - add_directory(library_dirs, os.path.join(prefix, "lib")) -@@ -199,22 +176,6 @@ +- add_directory(library_dirs, os.path.join(prefix, "lib")) +- add_directory(include_dirs, os.path.join(prefix, "include")) ++ add_directory(library_dirs, os.environ.get('PYTHON_LINK_ROOT')) ++ add_directory(include_dirs, os.environ.get('PYTHON_INCLUDE_ROOT')) + + # + # locate tkinter libraries +@@ -199,22 +176,6 @@ class pil_build_ext(build_ext): add_directory(include_dirs, include_root) # @@ -68,7 +74,7 @@ diff -Naur pil/setup.py b/setup.py # insert new dirs *before* default libs, to avoid conflicts # between Python PYD stub libs and real libraries -@@ -299,8 +260,6 @@ +@@ -299,8 +260,6 @@ class pil_build_ext(build_ext): defs.append(("HAVE_LIBZ", None)) if sys.platform == "win32": libs.extend(["kernel32", "user32", "gdi32"]) diff --git a/p4a/pythonforandroid/recipes/png/__init__.py b/p4a/pythonforandroid/recipes/png/__init__.py index d912c669..5b696888 100644 --- a/p4a/pythonforandroid/recipes/png/__init__.py +++ b/p4a/pythonforandroid/recipes/png/__init__.py @@ -2,12 +2,18 @@ from pythonforandroid.recipe import NDKRecipe class PngRecipe(NDKRecipe): - name = 'png' - version = '1.6.15' - url = 'https://github.com/julienr/libpng-android/archive/{version}.zip' + name = 'png' + # This version is the last `sha commit` published in the repo (it's more + # than one year old...) and it's for libpng version `1.6.29`. We set a + # commit for a version because the author of the github's repo never + # released/tagged it, despite He performed the necessary changes in + # master branch. + version = 'b43b4c6' - generated_libraries = ['libpng.a'] + # TODO: Try to move the repo to mainline + url = 'https://github.com/julienr/libpng-android/archive/{version}.zip' + + generated_libraries = ['libpng.a'] recipe = PngRecipe() - diff --git a/p4a/pythonforandroid/recipes/preppy/__init__.py b/p4a/pythonforandroid/recipes/preppy/__init__.py new file mode 100644 index 00000000..40afd681 --- /dev/null +++ b/p4a/pythonforandroid/recipes/preppy/__init__.py @@ -0,0 +1,12 @@ +from pythonforandroid.recipe import PythonRecipe + + +class PreppyRecipe(PythonRecipe): + version = '27b7085' + url = 'https://bitbucket.org/rptlab/preppy/get/{version}.tar.gz' + depends = [] + patches = ['fix-setup.patch'] + call_hostpython_via_targetpython = False + + +recipe = PreppyRecipe() diff --git a/p4a/pythonforandroid/recipes/preppy/fix-setup.patch b/p4a/pythonforandroid/recipes/preppy/fix-setup.patch new file mode 100644 index 00000000..400614d3 --- /dev/null +++ b/p4a/pythonforandroid/recipes/preppy/fix-setup.patch @@ -0,0 +1,44 @@ +--- a/setup.py 2017-11-20 13:53:42.000000000 +0000 ++++ b/setup.py 2017-11-20 14:00:44.862203526 +0000 +@@ -15,35 +15,6 @@ + + import preppy + version = preppy.VERSION +- scriptsPath=os.path.join(pkgDir,'build','scripts') +- +- def makeScript(modName): +- try: +- bat=sys.platform in ('win32','amd64') +- scriptPath=os.path.join(scriptsPath,modName+(bat and '.bat' or '')) +- exePath=sys.executable +- f = open(scriptPath,'w') +- try: +- if bat: +- text = '@echo off\nrem startup script for %s-%s\n"%s" -m "%s" %%*\n' % (modName,version,exePath,modName) +- else: +- text = '#!/bin/sh\n#startup script for %s-%s\nexec "%s" -m "%s" $*\n' % (modName,version,exePath,modName) +- f.write(text) +- finally: +- f.close() +- except: +- print('script for %s not created or erroneous' % modName) +- import traceback +- traceback.print_exc(file=sys.stdout) +- return None +- print('Created "%s"' % scriptPath) +- return scriptPath +- +- scripts = [] +- if not os.path.isdir(scriptsPath): os.makedirs(scriptsPath) +- scripts.extend(filter(None,[ +- makeScript('preppy'), +- ])) + + setup(name='preppy', + version=version, +@@ -52,5 +23,4 @@ + author_email='andy@reportlab.com', + url='http://bitbucket.org/rptlab/preppy', + py_modules=['preppy'], +- scripts=scripts, + ) diff --git a/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py index 53ac8fd0..30ca030a 100644 --- a/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py +++ b/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py @@ -1,109 +1,146 @@ from pythonforandroid.recipe import PythonRecipe -from pythonforandroid.logger import shprint +from pythonforandroid.logger import shprint, info_notify from pythonforandroid.util import current_directory, shutil -from pythonforandroid.util import ensure_dir -from os.path import exists, join, dirname +from os.path import exists, join import sh from multiprocessing import cpu_count - - from pythonforandroid.toolchain import info +import sys +import os class ProtobufCppRecipe(PythonRecipe): - name = 'protobuf_cpp' - version = '3.1.0' - url = 'https://github.com/google/protobuf/releases/download/v{version}/protobuf-python-{version}.tar.gz' - call_hostpython_via_targetpython = False - depends = ['cffi', 'setuptools'] - site_packages_name = 'google/protobuf/pyext' + name = 'protobuf_cpp' + version = '3.6.1' + url = 'https://github.com/google/protobuf/releases/download/v{version}/protobuf-python-{version}.tar.gz' + call_hostpython_via_targetpython = False + depends = ['cffi', 'setuptools'] + site_packages_name = 'google/protobuf/pyext' + protoc_dir = None - def build_arch(self, arch): - env = self.get_recipe_env(arch) + def prebuild_arch(self, arch): + super(ProtobufCppRecipe, self).prebuild_arch(arch) - # Build libproto.a - with current_directory(self.get_build_dir(arch.arch)): - env['HOSTARCH'] = 'arm-eabi' - env['BUILDARCH'] = shprint(sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0] + patch_mark = join(self.get_build_dir(arch.arch), '.protobuf-patched') + if self.ctx.python_recipe.name == 'python3' and not exists(patch_mark): + self.apply_patch('fix-python3-compatibility.patch', arch.arch) + shprint(sh.touch, patch_mark) - if not exists('configure'): - shprint(sh.Command('./autogen.sh'), _env=env) + # During building, host needs to transpile .proto files to .py + # ideally with the same version as protobuf runtime, or with an older one. + # Because protoc is compiled for target (i.e. Android), we need an other binary + # which can be run by host. + # To make it easier, we download prebuild protoc binary adapted to the platform - shprint(sh.Command('./configure'), - '--host={}'.format(env['HOSTARCH']), - '--enable-shared', - _env=env) + info_notify("Downloading protoc compiler for your platform") + url_prefix = "https://github.com/protocolbuffers/protobuf/releases/download/v{version}".format(version=self.version) + if sys.platform.startswith('linux'): + info_notify("GNU/Linux detected") + filename = "protoc-{version}-linux-x86_64.zip".format(version=self.version) + elif sys.platform.startswith('darwin'): + info_notify("Mac OS X detected") + filename = "protoc-{version}-osx-x86_64.zip".format(version=self.version) + else: + info_notify("Your platform is not supported, but recipe can still " + "be built if you have a valid protoc (<={version}) in " + "your path".format(version=self.version)) + return - with current_directory(join(self.get_build_dir(arch.arch), 'src')): - shprint(sh.make, 'libprotobuf.la', '-j'+str(cpu_count()), _env=env) - shprint(sh.cp, '.libs/libprotobuf.a', join(self.ctx.get_libs_dir(arch.arch), 'libprotobuf.a')) + protoc_url = join(url_prefix, filename) + self.protoc_dir = join(self.ctx.build_dir, "tools", "protoc") + if os.path.exists(join(self.protoc_dir, "bin", "protoc")): + info_notify("protoc found, no download needed") + return + try: + os.makedirs(self.protoc_dir) + except OSError as e: + # if dir already exists (errno 17), we ignore the error + if e.errno != 17: + raise e + info_notify("Will download into {dest_dir}".format(dest_dir=self.protoc_dir)) + self.download_file(protoc_url, join(self.protoc_dir, filename)) + with current_directory(self.protoc_dir): + shprint(sh.unzip, join(self.protoc_dir, filename)) - # Copy stl library - shutil.copyfile(self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/libgnustl_shared.so', - join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so')) + def build_arch(self, arch): + env = self.get_recipe_env(arch) - # Build python bindings and _message.so - with current_directory(join(self.get_build_dir(arch.arch), 'python')): - hostpython = sh.Command(self.hostpython_location) - shprint(hostpython, - 'setup.py', - 'build_ext', - '--cpp_implementation' - , _env=env) + # Build libproto.a + with current_directory(self.get_build_dir(arch.arch)): + env['HOSTARCH'] = 'arm-eabi' + env['BUILDARCH'] = shprint(sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0] - # Install python bindings - self.install_python_package(arch) + if not exists('configure'): + shprint(sh.Command('./autogen.sh'), _env=env) + shprint(sh.Command('./configure'), + '--host={}'.format(env['HOSTARCH']), + '--enable-shared', + _env=env) - def install_python_package(self, arch): - env = self.get_recipe_env(arch) + with current_directory(join(self.get_build_dir(arch.arch), 'src')): + shprint(sh.make, 'libprotobuf.la', '-j'+str(cpu_count()), _env=env) + shprint(sh.cp, '.libs/libprotobuf.a', join(self.ctx.get_libs_dir(arch.arch), 'libprotobuf.a')) - info('Installing {} into site-packages'.format(self.name)) + # Copy stl library + shutil.copyfile( + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/libgnustl_shared.so', + join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so')) - with current_directory(join(self.get_build_dir(arch.arch), 'python')): - hostpython = sh.Command(self.hostpython_location) + # Build python bindings and _message.so + with current_directory(join(self.get_build_dir(arch.arch), 'python')): + hostpython = sh.Command(self.hostpython_location) + shprint(hostpython, + 'setup.py', + 'build_ext', + '--cpp_implementation', _env=env) - if self.ctx.python_recipe.from_crystax: - hpenv = env.copy() - shprint(hostpython, 'setup.py', 'install', '-O2', - '--root={}'.format(self.ctx.get_python_install_dir()), - '--install-lib=.', - '--cpp_implementation', - _env=hpenv, *self.setup_extra_args) - else: - hppath = join(dirname(self.hostpython_location), 'Lib', - 'site-packages') - hpenv = env.copy() - if 'PYTHONPATH' in hpenv: - hpenv['PYTHONPATH'] = ':'.join([hppath] + - hpenv['PYTHONPATH'].split(':')) - else: - hpenv['PYTHONPATH'] = hppath - shprint(hostpython, 'setup.py', 'install', '-O2', - '--root={}'.format(self.ctx.get_python_install_dir()), - '--install-lib=lib/python2.7/site-packages', - '--cpp_implementation', - _env=hpenv, *self.setup_extra_args) + # Install python bindings + self.install_python_package(arch) + # Create __init__.py which is missing (cf. https://github.com/protocolbuffers/protobuf/issues/1296 + # and https://stackoverflow.com/questions/13862562/google-protocol-buffers-not-found-when-trying-to-freeze-python-app) + open(join(self.ctx.get_site_packages_dir(), 'google', '__init__.py'), 'a').close() - def get_recipe_env(self, arch): - env = super(ProtobufCppRecipe, self).get_recipe_env(arch) - env['PROTOC'] = '/home/fipo/soft/protobuf-3.1.0/src/protoc' - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE' - env['CFLAGS'] += ' -I' + self.ctx.ndk_dir + '/platforms/android-' + str( - self.ctx.android_api) + '/arch-' + arch.arch.replace('eabi', '') + '/usr/include' + \ - ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/include' + \ - ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/include' + \ - ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' - env['CXXFLAGS'] = env['CFLAGS'] - env['CXXFLAGS'] += ' -frtti' - env['CXXFLAGS'] += ' -fexceptions' - env['LDFLAGS'] += ' -L' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + \ - ' -lgnustl_shared -lpython2.7' + def install_python_package(self, arch): + env = self.get_recipe_env(arch) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - return env + info('Installing {} into site-packages'.format(self.name)) + + with current_directory(join(self.get_build_dir(arch.arch), 'python')): + hostpython = sh.Command(self.hostpython_location) + + hpenv = env.copy() + shprint(hostpython, 'setup.py', 'install', '-O2', + '--root={}'.format(self.ctx.get_python_install_dir()), + '--install-lib=.', + '--cpp_implementation', + _env=hpenv, *self.setup_extra_args) + + def get_recipe_env(self, arch): + env = super(ProtobufCppRecipe, self).get_recipe_env(arch) + if self.protoc_dir is not None: + # we need protoc with binary for host platform + env['PROTOC'] = join(self.protoc_dir, 'bin', 'protoc') + env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE' + env['CFLAGS'] += ( + ' -I' + self.ctx.ndk_dir + '/platforms/android-' + + str(self.ctx.android_api) + + '/arch-' + arch.arch.replace('eabi', '') + '/usr/include' + + ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + + self.ctx.toolchain_version + '/include' + + ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + + self.ctx.toolchain_version + '/libs/' + arch.arch + '/include') + env['CFLAGS'] += ' -std=gnu++11' + env['CXXFLAGS'] = env['CFLAGS'] + env['CXXFLAGS'] += ' -frtti' + env['CXXFLAGS'] += ' -fexceptions' + env['LDFLAGS'] += ( + ' -lgnustl_shared -landroid -llog' + + ' -L' + self.ctx.ndk_dir + + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + + '/libs/' + arch.arch) + return env recipe = ProtobufCppRecipe() diff --git a/p4a/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch b/p4a/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch new file mode 100644 index 00000000..e77debaa --- /dev/null +++ b/p4a/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch @@ -0,0 +1,91 @@ +From 539bc017a62f91bdf7c547b58948cb5a2f59d918 Mon Sep 17 00:00:00 2001 +From: Ben Webb +Date: Thu, 12 Jul 2018 10:58:10 -0700 +Subject: [PATCH] Add Python 3.7 compatibility (#4862) + +Compilation of Python wrappers fails with Python 3.7 because +the Python folks changed their C API such that +PyUnicode_AsUTF8AndSize() now returns a const char* rather +than a char*. Add a patch to work around. Relates #4086. +--- + python/google/protobuf/pyext/descriptor.cc | 2 +- + python/google/protobuf/pyext/descriptor_containers.cc | 2 +- + python/google/protobuf/pyext/descriptor_pool.cc | 2 +- + python/google/protobuf/pyext/extension_dict.cc | 2 +- + python/google/protobuf/pyext/message.cc | 4 ++-- + 5 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc +index 8af0cb1289..19a1c38a62 100644 +--- a/python/google/protobuf/pyext/descriptor.cc ++++ b/python/google/protobuf/pyext/descriptor.cc +@@ -56,7 +56,7 @@ + #endif + #define PyString_AsStringAndSize(ob, charpp, sizep) \ + (PyUnicode_Check(ob)? \ +- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \ ++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \ + PyBytes_AsStringAndSize(ob, (charpp), (sizep))) + #endif + +diff --git a/python/google/protobuf/pyext/descriptor_containers.cc b/python/google/protobuf/pyext/descriptor_containers.cc +index bc007f7efa..0153664f50 100644 +--- a/python/google/protobuf/pyext/descriptor_containers.cc ++++ b/python/google/protobuf/pyext/descriptor_containers.cc +@@ -66,7 +66,7 @@ + #endif + #define PyString_AsStringAndSize(ob, charpp, sizep) \ + (PyUnicode_Check(ob)? \ +- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \ ++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \ + PyBytes_AsStringAndSize(ob, (charpp), (sizep))) + #endif + +diff --git a/python/google/protobuf/pyext/descriptor_pool.cc b/python/google/protobuf/pyext/descriptor_pool.cc +index 95882aeb35..962accc6e9 100644 +--- a/python/google/protobuf/pyext/descriptor_pool.cc ++++ b/python/google/protobuf/pyext/descriptor_pool.cc +@@ -48,7 +48,7 @@ + #endif + #define PyString_AsStringAndSize(ob, charpp, sizep) \ + (PyUnicode_Check(ob)? \ +- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \ ++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \ + PyBytes_AsStringAndSize(ob, (charpp), (sizep))) + #endif + +diff --git a/python/google/protobuf/pyext/extension_dict.cc b/python/google/protobuf/pyext/extension_dict.cc +index 018b5c2c49..174c5470c2 100644 +--- a/python/google/protobuf/pyext/extension_dict.cc ++++ b/python/google/protobuf/pyext/extension_dict.cc +@@ -53,7 +53,7 @@ + #endif + #define PyString_AsStringAndSize(ob, charpp, sizep) \ + (PyUnicode_Check(ob)? \ +- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \ ++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \ + PyBytes_AsStringAndSize(ob, (charpp), (sizep))) + #endif + +diff --git a/python/google/protobuf/pyext/message.cc b/python/google/protobuf/pyext/message.cc +index 5893533adf..31094b7e10 100644 +--- a/python/google/protobuf/pyext/message.cc ++++ b/python/google/protobuf/pyext/message.cc +@@ -79,7 +79,7 @@ + (PyUnicode_Check(ob)? PyUnicode_AsUTF8(ob): PyBytes_AsString(ob)) + #define PyString_AsStringAndSize(ob, charpp, sizep) \ + (PyUnicode_Check(ob)? \ +- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \ ++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \ + PyBytes_AsStringAndSize(ob, (charpp), (sizep))) + #endif + #endif +@@ -1529,7 +1529,7 @@ PyObject* HasField(CMessage* self, PyObject* arg) { + return NULL; + } + #else +- field_name = PyUnicode_AsUTF8AndSize(arg, &size); ++ field_name = const_cast(PyUnicode_AsUTF8AndSize(arg, &size)); + if (!field_name) { + return NULL; + } diff --git a/p4a/pythonforandroid/recipes/psycopg2/__init__.py b/p4a/pythonforandroid/recipes/psycopg2/__init__.py index 1c9d227b..aaf5a332 100644 --- a/p4a/pythonforandroid/recipes/psycopg2/__init__.py +++ b/p4a/pythonforandroid/recipes/psycopg2/__init__.py @@ -1,12 +1,20 @@ -from pythonforandroid.toolchain import PythonRecipe, current_directory, shprint +from pythonforandroid.recipe import PythonRecipe +from pythonforandroid.toolchain import current_directory, shprint import sh class Psycopg2Recipe(PythonRecipe): + """ + Requires `libpq-dev` system dependency e.g. for `pg_config` binary. + If you get `nl_langinfo` symbol runtime error, make sure you're running on + `ANDROID_API` (`ndk-api`) >= 26, see: + https://github.com/kivy/python-for-android/issues/1711#issuecomment-465747557 + """ version = 'latest' url = 'http://initd.org/psycopg/tarballs/psycopg2-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'libpq'] + depends = ['libpq'] site_packages_name = 'psycopg2' + call_hostpython_via_targetpython = False def prebuild_arch(self, arch): libdir = self.ctx.get_libs_dir(arch.arch) @@ -36,6 +44,7 @@ class Psycopg2Recipe(PythonRecipe): _env=env) shprint(hostpython, 'setup.py', 'install', '-O2', '--root={}'.format(self.ctx.get_python_install_dir()), - '--install-lib=lib/python2.7/site-packages', _env=env) + '--install-lib=.', _env=env) + recipe = Psycopg2Recipe() diff --git a/p4a/pythonforandroid/recipes/pyaml/__init__.py b/p4a/pythonforandroid/recipes/pyaml/__init__.py index d3d1eb91..84401757 100644 --- a/p4a/pythonforandroid/recipes/pyaml/__init__.py +++ b/p4a/pythonforandroid/recipes/pyaml/__init__.py @@ -1,11 +1,12 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class PyamlRecipe(PythonRecipe): version = "15.8.2" url = 'https://pypi.python.org/packages/source/p/pyaml/pyaml-{version}.tar.gz' - depends = [('python2', 'python3crystax'), "setuptools"] + depends = ["setuptools"] site_packages_name = 'yaml' call_hostpython_via_targetpython = False + recipe = PyamlRecipe() diff --git a/p4a/pythonforandroid/recipes/pyasn1/__init__.py b/p4a/pythonforandroid/recipes/pyasn1/__init__.py index 29670cab..9befc597 100644 --- a/p4a/pythonforandroid/recipes/pyasn1/__init__.py +++ b/p4a/pythonforandroid/recipes/pyasn1/__init__.py @@ -1,10 +1,11 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class PyASN1Recipe(PythonRecipe): - version = '0.1.8' + version = '0.4.5' url = 'https://pypi.python.org/packages/source/p/pyasn1/pyasn1-{version}.tar.gz' - depends = ['python2'] + depends = [] + recipe = PyASN1Recipe() diff --git a/p4a/pythonforandroid/recipes/pycparser/__init__.py b/p4a/pythonforandroid/recipes/pycparser/__init__.py index 0f879f48..6c82cf8a 100644 --- a/p4a/pythonforandroid/recipes/pycparser/__init__.py +++ b/p4a/pythonforandroid/recipes/pycparser/__init__.py @@ -2,15 +2,15 @@ from pythonforandroid.recipe import PythonRecipe class PycparserRecipe(PythonRecipe): - name = 'pycparser' - version = '2.14' - url = 'https://pypi.python.org/packages/source/p/pycparser/pycparser-{version}.tar.gz' + name = 'pycparser' + version = '2.14' + url = 'https://pypi.python.org/packages/source/p/pycparser/pycparser-{version}.tar.gz' - depends = [('python2', 'python3crystax'), 'setuptools'] + depends = ['setuptools'] - call_hostpython_via_targetpython = False + call_hostpython_via_targetpython = False - install_in_hostpython = True + install_in_hostpython = True recipe = PycparserRecipe() diff --git a/p4a/pythonforandroid/recipes/pycrypto/__init__.py b/p4a/pythonforandroid/recipes/pycrypto/__init__.py index 03864337..e8bfab26 100644 --- a/p4a/pythonforandroid/recipes/pycrypto/__init__.py +++ b/p4a/pythonforandroid/recipes/pycrypto/__init__.py @@ -1,31 +1,30 @@ - +from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe from pythonforandroid.toolchain import ( - CompiledComponentsPythonRecipe, - Recipe, current_directory, info, shprint, ) -from os.path import join import sh class PyCryptoRecipe(CompiledComponentsPythonRecipe): - version = '2.6.1' - url = 'https://pypi.python.org/packages/source/p/pycrypto/pycrypto-{version}.tar.gz' - depends = ['openssl', 'python2'] + version = '2.7a1' + url = 'https://github.com/dlitz/pycrypto/archive/v{version}.zip' + depends = ['openssl', ('python2', 'python3')] site_packages_name = 'Crypto' - + call_hostpython_via_targetpython = False patches = ['add_length.patch'] - def get_recipe_env(self, arch=None): + def get_recipe_env(self, arch=None, clang=True): env = super(PyCryptoRecipe, self).get_recipe_env(arch) - openssl_build_dir = Recipe.get_recipe('openssl', self.ctx).get_build_dir(arch.arch) - env['CC'] = '%s -I%s' % (env['CC'], join(openssl_build_dir, 'include')) - env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format( - self.ctx.get_libs_dir(arch.arch) + - '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format( - openssl_build_dir) + openssl_recipe = Recipe.get_recipe('openssl', self.ctx) + env['CC'] = env['CC'] + openssl_recipe.include_flags(arch) + + env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch)) + env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir) + env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch) + env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags() + env['EXTRA_CFLAGS'] = '--host linux-armv' env['ac_cv_func_malloc_0_nonnull'] = 'yes' return env @@ -41,4 +40,5 @@ class PyCryptoRecipe(CompiledComponentsPythonRecipe): '--enable-shared', _env=env) super(PyCryptoRecipe, self).build_compiled_components(arch) + recipe = PyCryptoRecipe() diff --git a/p4a/pythonforandroid/recipes/pycryptodome/__init__.py b/p4a/pythonforandroid/recipes/pycryptodome/__init__.py index 3fa007be..9418600a 100644 --- a/p4a/pythonforandroid/recipes/pycryptodome/__init__.py +++ b/p4a/pythonforandroid/recipes/pycryptodome/__init__.py @@ -2,10 +2,9 @@ from pythonforandroid.recipe import PythonRecipe class PycryptodomeRecipe(PythonRecipe): - version = 'v3.4.6' - url = 'https://github.com/Legrandin/pycryptodome/archive/{version}.tar.gz' - - depends = ['python2', 'setuptools', 'cffi'] + version = '3.6.3' + url = 'https://github.com/Legrandin/pycryptodome/archive/v{version}.tar.gz' + depends = ['setuptools', 'cffi'] recipe = PycryptodomeRecipe() diff --git a/p4a/pythonforandroid/recipes/pyethereum/__init__.py b/p4a/pythonforandroid/recipes/pyethereum/__init__.py index f08c0733..d18ad8ea 100644 --- a/p4a/pythonforandroid/recipes/pyethereum/__init__.py +++ b/p4a/pythonforandroid/recipes/pyethereum/__init__.py @@ -6,7 +6,7 @@ class PyethereumRecipe(PythonRecipe): url = 'https://github.com/ethereum/pyethereum/archive/{version}.tar.gz' depends = [ - 'python2', 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt' + 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt' ] call_hostpython_via_targetpython = False diff --git a/p4a/pythonforandroid/recipes/pygame/__init__.py b/p4a/pythonforandroid/recipes/pygame/__init__.py index 779da133..981fa445 100644 --- a/p4a/pythonforandroid/recipes/pygame/__init__.py +++ b/p4a/pythonforandroid/recipes/pygame/__init__.py @@ -1,17 +1,18 @@ from pythonforandroid.recipe import Recipe from pythonforandroid.util import current_directory, ensure_dir -from pythonforandroid.logger import debug, shprint, info -from os.path import exists, join +from pythonforandroid.logger import debug, shprint, info, warning +from os.path import join import sh import glob + class PygameRecipe(Recipe): name = 'pygame' version = '1.9.1' url = 'http://pygame.org/ftp/pygame-{version}release.tar.gz' - depends = ['python2', 'sdl'] + depends = ['python2legacy', 'sdl'] conflicts = ['sdl2'] patches = ['patches/fix-surface-access.patch', @@ -37,12 +38,10 @@ class PygameRecipe(Recipe): return shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'), join(self.get_build_dir(arch.arch), 'Setup')) - + def build_arch(self, arch): - # AND: I'm going to ignore any extra pythonrecipe or cythonrecipe behaviour for now - env = self.get_recipe_env(arch) - + env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/png -I{jni_path}/jpeg'.format( jni_path=join(self.ctx.bootstrap.build_dir, 'jni')) env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl/include -I{jni_path}/sdl_mixer'.format( @@ -51,7 +50,6 @@ class PygameRecipe(Recipe): jni_path=join(self.ctx.bootstrap.build_dir, 'jni')) debug('pygame cflags', env['CFLAGS']) - env['LDFLAGS'] = env['LDFLAGS'] + ' -L{libs_path} -L{src_path}/obj/local/{arch} -lm -lz'.format( libs_path=self.ctx.libs_dir, src_path=self.ctx.bootstrap.build_dir, arch=env['ARCH']) @@ -70,9 +68,7 @@ class PygameRecipe(Recipe): shprint(sh.find, build_lib[0], '-name', '*.o', '-exec', env['STRIP'], '{}', ';') - python_install_path = join(self.ctx.build_dir, 'python-install') - # AND: Should do some deleting here! - print('Should remove pygame tests etc. here, but skipping for now') + warning('Should remove pygame tests etc. here, but skipping for now') recipe = PygameRecipe() diff --git a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py b/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py index af7ec6b1..f18dd5bd 100644 --- a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py +++ b/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py @@ -1,15 +1,18 @@ -from pythonforandroid.toolchain import BootstrapNDKRecipe, current_directory, shprint, info +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.toolchain import current_directory, shprint, info from os.path import exists, join import sh import glob + class PygameJNIComponentsRecipe(BootstrapNDKRecipe): version = 'master' url = 'https://github.com/kivy/p4a-pygame-bootstrap-components/archive/{version}.zip' dir_name = 'bootstrap_components' + patches = ['jpeg-ndk15-plus.patch'] def prebuild_arch(self, arch): - super(PygameJNIComponentsRecipe, self).postbuild_arch(arch) + super(PygameJNIComponentsRecipe, self).prebuild_arch(arch) info('Unpacking pygame bootstrap JNI dir components') with current_directory(self.get_build_container_dir(arch)): @@ -22,6 +25,10 @@ class PygameJNIComponentsRecipe(BootstrapNDKRecipe): shprint(sh.mv, dirn, './') info('Unpacking was successful, deleting original container dir') shprint(sh.rm, '-rf', self.get_build_dir(arch)) - + + def apply_patches(self, arch, build_dir=None): + super(PygameJNIComponentsRecipe, self).apply_patches( + arch, build_dir=self.get_build_container_dir(arch.arch)) + recipe = PygameJNIComponentsRecipe() diff --git a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch b/p4a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch new file mode 100644 index 00000000..9992084f --- /dev/null +++ b/p4a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch @@ -0,0 +1,44 @@ +The distributed jpeg has troubles to be build with newer ndks, starting from +the introduction of the `unified headers` (ndk > 15). This patch allow us to +build the distributed `external jpeg` in sdl package, got the solution in here: +https://github.com/oNaiPs/droidVncServer/issues/53 +--- jni/jpeg/Android.mk.orig 2015-06-21 15:14:54.000000000 +0200 ++++ jni/jpeg/Android.mk 2019-01-14 10:57:06.384806168 +0100 +@@ -20,7 +20,7 @@ + endif + + # temp fix until we understand why this broke cnn.com +-#ANDROID_JPEG_NO_ASSEMBLER := true ++ANDROID_JPEG_NO_ASSEMBLER := true + + ifeq ($(strip $(ANDROID_JPEG_NO_ASSEMBLER)),true) + LOCAL_SRC_FILES += jidctint.c jidctfst.c +--- jni/jpeg/jidctfst.S.orig 2019-01-14 11:00:38.000000000 +0100 ++++ jni/jpeg/jidctfst.S 2019-01-14 11:00:56.844803970 +0100 +@@ -63,7 +63,7 @@ + + + jpeg_idct_ifast: +- PLD [r2, #0] ++ pld [r2, #0] + stmdb sp!, {r4,r5, r6,r7, r8,r9, r10,r11, r12,lr} + ldr r4, [sp, #4*10] + sub sp, #local_SIZE +@@ -256,7 +256,7 @@ + + HLoopStart: + // reset pointers +- PLD [sp, #off_WORKSPACE] ++ pld [sp, #off_WORKSPACE] + add ip, sp, #off_WORKSPACE + ldr r10, local_RANGE_TABLE + +@@ -268,7 +268,7 @@ + str r0, local_OUTPUT_BUF + add fp, r2, r1 + +- PLD [ip, #32] ++ pld [ip, #32] + ldmia ip!, {r0-r7} + + cmp r1, #0 diff --git a/p4a/pythonforandroid/recipes/pyicu/__init__.py b/p4a/pythonforandroid/recipes/pyicu/__init__.py index 3e6627e1..98ec7b79 100644 --- a/p4a/pythonforandroid/recipes/pyicu/__init__.py +++ b/p4a/pythonforandroid/recipes/pyicu/__init__.py @@ -2,14 +2,13 @@ import os import sh from os.path import join from pythonforandroid.recipe import CompiledComponentsPythonRecipe -from pythonforandroid.util import current_directory from pythonforandroid.toolchain import shprint, info class PyICURecipe(CompiledComponentsPythonRecipe): version = '1.9.2' url = 'https://pypi.python.org/packages/source/P/PyICU/PyICU-{version}.tar.gz' - depends = [('python2', 'python3crystax'), "icu"] + depends = ["icu"] patches = ['locale.patch', 'icu.patch'] def get_recipe_env(self, arch): diff --git a/p4a/pythonforandroid/recipes/pyjnius/__init__.py b/p4a/pythonforandroid/recipes/pyjnius/__init__.py index 7aad8d46..8aeac6c6 100644 --- a/p4a/pythonforandroid/recipes/pyjnius/__init__.py +++ b/p4a/pythonforandroid/recipes/pyjnius/__init__.py @@ -1,15 +1,17 @@ - -from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, info -from pythonforandroid.patching import will_build, check_any +from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.toolchain import shprint, current_directory, info +from pythonforandroid.patching import will_build import sh from os.path import join class PyjniusRecipe(CythonRecipe): - version = 'master' + # "6553ad4" is one commit after last release (1.2.0) + # it fixes method resolution, required for resolving requestPermissions() + version = '6553ad4' url = 'https://github.com/kivy/pyjnius/archive/{version}.zip' name = 'pyjnius' - depends = [('python2', 'python3crystax'), ('sdl2', 'sdl', 'genericndkbuild'), 'six'] + depends = [('genericndkbuild', 'sdl2', 'sdl'), 'six'] site_packages_name = 'jnius' patches = [('sdl2_jnienv_getter.patch', will_build('sdl2')), diff --git a/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch b/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch index 50c62cb3..ff269941 100644 --- a/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch +++ b/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch @@ -16,10 +16,10 @@ index 740510f..0c8e55f 100644 +++ b/setup.py @@ -53,7 +53,7 @@ except ImportError: - if platform == 'android': + if PLATFORM == 'android': # for android, we use SDL... -- libraries = ['sdl', 'log'] -+ libraries = ['main', 'log'] - library_dirs = ['libs/' + getenv('ARCH')] - elif platform == 'darwin': +- LIBRARIES = ['sdl', 'log'] ++ LIBRARIES = ['main', 'log'] + LIBRARY_DIRS = ['libs/' + getenv('ARCH')] + elif PLATFORM == 'darwin': import subprocess diff --git a/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch b/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch index a3661389..d208e5f6 100644 --- a/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch +++ b/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch @@ -16,10 +16,10 @@ index 740510f..0c8e55f 100644 +++ b/setup.py @@ -53,7 +53,7 @@ except ImportError: - if platform == 'android': + if PLATFORM == 'android': # for android, we use SDL... -- libraries = ['sdl', 'log'] -+ libraries = ['SDL2', 'log'] - library_dirs = ['libs/' + getenv('ARCH')] - elif platform == 'darwin': +- LIBRARIES = ['sdl', 'log'] ++ LIBRARIES = ['SDL2', 'log'] + LIBRARY_DIRS = ['libs/' + getenv('ARCH')] + elif PLATFORM == 'darwin': import subprocess diff --git a/p4a/pythonforandroid/recipes/pyleveldb/__init__.py b/p4a/pythonforandroid/recipes/pyleveldb/__init__.py index c3305b07..61477092 100644 --- a/p4a/pythonforandroid/recipes/pyleveldb/__init__.py +++ b/p4a/pythonforandroid/recipes/pyleveldb/__init__.py @@ -1,35 +1,13 @@ -from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, shprint, shutil, current_directory -from os.path import join, exists -import sh +from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe -class PyLevelDBRecipe(CompiledComponentsPythonRecipe): + +class PyLevelDBRecipe(CppCompiledComponentsPythonRecipe): version = '0.193' url = 'https://pypi.python.org/packages/source/l/leveldb/leveldb-{version}.tar.gz' - depends = ['snappy', 'leveldb', 'hostpython2', 'python2', 'setuptools'] + depends = ['snappy', 'leveldb', ('hostpython2', 'hostpython3'), 'setuptools'] patches = ['bindings-only.patch'] - call_hostpython_via_targetpython = False # Due to setuptools + call_hostpython_via_targetpython = False # Due to setuptools site_packages_name = 'leveldb' - def build_arch(self, arch): - env = self.get_recipe_env(arch) - with current_directory(self.get_build_dir(arch.arch)): - # Remove source in this pypi package - sh.rm('-rf', 'leveldb', 'leveldb.egg-info', 'snappy') - # Use source from leveldb recipe - sh.ln('-s', self.get_recipe('leveldb', self.ctx).get_build_dir(arch.arch), 'leveldb') - # Build and install python bindings - super(PyLevelDBRecipe, self).build_arch(arch) - - def get_recipe_env(self, arch): - env = super(PyLevelDBRecipe, self).get_recipe_env(arch) - # Copy environment from leveldb recipe - env.update(self.get_recipe('leveldb', self.ctx).get_recipe_env(arch)) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' - # Set linker to use the correct gcc - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' - env['LDFLAGS'] += ' -lpython2.7' + \ - ' -lleveldb' - return env recipe = PyLevelDBRecipe() diff --git a/p4a/pythonforandroid/recipes/pymunk/__init__.py b/p4a/pythonforandroid/recipes/pymunk/__init__.py index 73e4b786..b72b85b0 100644 --- a/p4a/pythonforandroid/recipes/pymunk/__init__.py +++ b/p4a/pythonforandroid/recipes/pymunk/__init__.py @@ -1,25 +1,22 @@ -from pythonforandroid.toolchain import PythonRecipe -from pythonforandroid.toolchain import CythonRecipe +from os.path import join from pythonforandroid.recipe import CompiledComponentsPythonRecipe -from pythonforandroid.logger import info -import os.path class PymunkRecipe(CompiledComponentsPythonRecipe): name = "pymunk" - version = '5.2.0' - url = 'https://pypi.python.org/packages/5e/bd/e67edcffdee3d0a1e3ebf0050bb9746a61d616f5502ceedddf0f7fd0a896/pymunk-5.2.0.zip' - depends = [('python2', 'python3crystax'), 'cffi', 'setuptools'] + version = '5.3.2' + url = 'https://pypi.python.org/packages/source/p/pymunk/pymunk-{version}.zip' + depends = ['cffi', 'setuptools'] call_hostpython_via_targetpython = False def get_recipe_env(self, arch): env = super(PymunkRecipe, self).get_recipe_env(arch) env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - arch_noeabi = arch.arch.replace('eabi', '') env['LDFLAGS'] += " -shared -llog" - env['LDFLAGS'] += " -landroid -lpython2.7" - env['LDFLAGS'] += " --sysroot={ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}".format( - ctx=self.ctx, arch_noeabi=arch_noeabi) + env['LDFLAGS'] += ' -L{}'.format(join(self.ctx.ndk_platform, 'usr', 'lib')) + env['LDFLAGS'] += " --sysroot={}".format(self.ctx.ndk_platform) + env['LIBS'] = env.get('LIBS', '') + ' -landroid' return env + recipe = PymunkRecipe() diff --git a/p4a/pythonforandroid/recipes/pynacl/__init__.py b/p4a/pythonforandroid/recipes/pynacl/__init__.py new file mode 100644 index 00000000..eb9ca2df --- /dev/null +++ b/p4a/pythonforandroid/recipes/pynacl/__init__.py @@ -0,0 +1,29 @@ +from pythonforandroid.recipe import CompiledComponentsPythonRecipe +import os + + +class PyNaCLRecipe(CompiledComponentsPythonRecipe): + name = 'pynacl' + version = '1.3.0' + url = 'https://pypi.python.org/packages/source/P/PyNaCl/PyNaCl-{version}.tar.gz' + + depends = [('hostpython2', 'hostpython3'), 'six', 'setuptools', 'cffi', 'libsodium'] + call_hostpython_via_targetpython = False + + def get_recipe_env(self, arch): + env = super(PyNaCLRecipe, self).get_recipe_env(arch) + env['SODIUM_INSTALL'] = 'system' + + libsodium_build_dir = self.get_recipe( + 'libsodium', self.ctx).get_build_dir(arch.arch) + env['CFLAGS'] += ' -I{}'.format(os.path.join(libsodium_build_dir, + 'src/libsodium/include')) + env['LDFLAGS'] += ' -L{}'.format( + self.ctx.get_libs_dir(arch.arch) + + '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format( + libsodium_build_dir) + + return env + + +recipe = PyNaCLRecipe() diff --git a/p4a/pythonforandroid/recipes/pyogg/__init__.py b/p4a/pythonforandroid/recipes/pyogg/__init__.py new file mode 100644 index 00000000..70ea435c --- /dev/null +++ b/p4a/pythonforandroid/recipes/pyogg/__init__.py @@ -0,0 +1,14 @@ +from pythonforandroid.recipe import PythonRecipe +from os.path import join + + +class PyOggRecipe(PythonRecipe): + version = '0.6.4a1' + url = 'https://files.pythonhosted.org/packages/source/p/pyogg/PyOgg-{version}.tar.gz' + depends = ['libogg', 'libvorbis', 'setuptools'] + patches = [join('patches', 'fix-find-lib.patch')] + + call_hostpython_via_targetpython = False + + +recipe = PyOggRecipe() diff --git a/p4a/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch b/p4a/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch new file mode 100644 index 00000000..0db7bfd1 --- /dev/null +++ b/p4a/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch @@ -0,0 +1,13 @@ +diff --git a/pyogg/library_loader.py b/pyogg/library_loader.py +index c2ba36c..383331a 100644 +--- a/pyogg/library_loader.py ++++ b/pyogg/library_loader.py +@@ -54,7 +54,7 @@ def load_other(name, paths = None): + except: + pass + else: +- for path in [os.getcwd(), _here]: ++ for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]: + for style in other_styles: + candidate = os.path.join(path, style.format(name)) + if os.path.exists(candidate): diff --git a/p4a/pythonforandroid/recipes/pyopenal/__init__.py b/p4a/pythonforandroid/recipes/pyopenal/__init__.py new file mode 100644 index 00000000..c42cd096 --- /dev/null +++ b/p4a/pythonforandroid/recipes/pyopenal/__init__.py @@ -0,0 +1,14 @@ +from pythonforandroid.recipe import PythonRecipe +from os.path import join + + +class PyOpenALRecipe(PythonRecipe): + version = '0.7.3a1' + url = 'https://files.pythonhosted.org/packages/source/p/pyopenal/PyOpenAL-{version}.tar.gz' + depends = ['openal', 'numpy', 'setuptools'] + patches = [join('patches', 'fix-find-lib.patch')] + + call_hostpython_via_targetpython = False + + +recipe = PyOpenALRecipe() diff --git a/p4a/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch b/p4a/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch new file mode 100644 index 00000000..e798bd12 --- /dev/null +++ b/p4a/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch @@ -0,0 +1,13 @@ +diff --git a/openal/library_loader.py b/openal/library_loader.py +index be2485c..e8c6cd2 100644 +--- a/openal/library_loader.py ++++ b/openal/library_loader.py +@@ -56,7 +56,7 @@ class ExternalLibrary: + except: + pass + else: +- for path in [os.getcwd(), _here]: ++ for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]: + for style in ExternalLibrary.other_styles: + candidate = os.path.join(path, style.format(name)) + if os.path.exists(candidate) and os.path.isfile(candidate): diff --git a/p4a/pythonforandroid/recipes/pyopenssl/__init__.py b/p4a/pythonforandroid/recipes/pyopenssl/__init__.py index dde2e1ed..092a3105 100644 --- a/p4a/pythonforandroid/recipes/pyopenssl/__init__.py +++ b/p4a/pythonforandroid/recipes/pyopenssl/__init__.py @@ -1,11 +1,11 @@ -from pythonforandroid.toolchain import PythonRecipe +from pythonforandroid.recipe import PythonRecipe class PyOpenSSLRecipe(PythonRecipe): - version = '0.14' + version = '19.0.0' url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz' - depends = ['openssl', 'python2', 'setuptools'] + depends = ['openssl', 'setuptools'] site_packages_name = 'OpenSSL' call_hostpython_via_targetpython = False diff --git a/p4a/pythonforandroid/recipes/pyproj/__init__.py b/p4a/pythonforandroid/recipes/pyproj/__init__.py index 3caec1da..71b272d1 100644 --- a/p4a/pythonforandroid/recipes/pyproj/__init__.py +++ b/p4a/pythonforandroid/recipes/pyproj/__init__.py @@ -4,8 +4,8 @@ from pythonforandroid.recipe import CythonRecipe class PyProjRecipe(CythonRecipe): version = '1.9.5.1' url = 'https://github.com/jswhit/pyproj/archive/master.zip' - depends = ['python2', 'setuptools'] + depends = ['setuptools'] call_hostpython_via_targetpython = False - + recipe = PyProjRecipe() diff --git a/p4a/pythonforandroid/recipes/pyrxp/__init__.py b/p4a/pythonforandroid/recipes/pyrxp/__init__.py new file mode 100644 index 00000000..09b1804a --- /dev/null +++ b/p4a/pythonforandroid/recipes/pyrxp/__init__.py @@ -0,0 +1,11 @@ +from pythonforandroid.recipe import CompiledComponentsPythonRecipe + + +class PyRXPURecipe(CompiledComponentsPythonRecipe): + version = '2a02cecc87b9' + url = 'https://bitbucket.org/rptlab/pyrxp/get/{version}.tar.gz' + depends = [] + patches = [] + + +recipe = PyRXPURecipe() diff --git a/p4a/pythonforandroid/recipes/pysdl2/__init__.py b/p4a/pythonforandroid/recipes/pysdl2/__init__.py index cd22b639..e0df9dc5 100644 --- a/p4a/pythonforandroid/recipes/pysdl2/__init__.py +++ b/p4a/pythonforandroid/recipes/pysdl2/__init__.py @@ -1,8 +1,6 @@ -from pythonforandroid.toolchain import PythonRecipe, shprint, current_directory, ArchARM -from os.path import exists, join -import sh -import glob +from pythonforandroid.recipe import PythonRecipe + class PySDL2Recipe(PythonRecipe): version = '0.9.3' diff --git a/p4a/pythonforandroid/recipes/pysha3/__init__.py b/p4a/pythonforandroid/recipes/pysha3/__init__.py index 3b98d593..35cfff84 100644 --- a/p4a/pythonforandroid/recipes/pysha3/__init__.py +++ b/p4a/pythonforandroid/recipes/pysha3/__init__.py @@ -1,11 +1,30 @@ +import os from pythonforandroid.recipe import PythonRecipe +# TODO: CompiledComponentsPythonRecipe class Pysha3Recipe(PythonRecipe): version = '1.0.2' url = 'https://github.com/tiran/pysha3/archive/{version}.tar.gz' + depends = ['setuptools'] + call_hostpython_via_targetpython = False - depends = ['python2', 'setuptools'] + def get_recipe_env(self, arch=None, with_flags_in_cc=True): + env = super(Pysha3Recipe, self).get_recipe_env(arch, with_flags_in_cc) + # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS + env['CPPFLAGS'] = env['CFLAGS'] + if self.ctx.ndk == 'crystax': + env['CPPFLAGS'] += ' -I{}/sources/python/{}/include/python/'.format( + self.ctx.ndk_dir, self.ctx.python_recipe.version[0:3]) + env['CFLAGS'] = '' + # LDFLAGS may only be used to specify linker flags, for libraries use LIBS + env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '').replace('-lcrystax', '') + env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)) + env['LIBS'] = ' -lm' + if self.ctx.ndk == 'crystax': + env['LIBS'] += ' -lcrystax -lpython{}m'.format(self.ctx.python_recipe.version[0:3]) + env['LDSHARED'] += env['LIBS'] + return env recipe = Pysha3Recipe() diff --git a/p4a/pythonforandroid/recipes/python2/__init__.py b/p4a/pythonforandroid/recipes/python2/__init__.py index c22c5f52..beba2b65 100644 --- a/p4a/pythonforandroid/recipes/python2/__init__.py +++ b/p4a/pythonforandroid/recipes/python2/__init__.py @@ -1,183 +1,74 @@ - -from pythonforandroid.recipe import TargetPythonRecipe, Recipe -from pythonforandroid.toolchain import shprint, current_directory, info -from pythonforandroid.patching import (is_linux, is_darwin, is_api_gt, - check_all, is_api_lt, is_ndk) -from os.path import exists, join, realpath +from os.path import join, exists +from pythonforandroid.recipe import Recipe +from pythonforandroid.python import GuestPythonRecipe +from pythonforandroid.logger import shprint import sh -class Python2Recipe(TargetPythonRecipe): - version = "2.7.2" - url = 'http://python.org/ftp/python/{version}/Python-{version}.tar.bz2' +class Python2Recipe(GuestPythonRecipe): + ''' + The python2's recipe. + + .. note:: This recipe can be built only against API 21+ + + .. versionchanged:: 0.6.0 + Updated to version 2.7.15 and the build process has been changed in + favour of the recently added class + :class:`~pythonforandroid.python.GuestPythonRecipe` + ''' + version = "2.7.15" + url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz' name = 'python2' depends = ['hostpython2'] - conflicts = ['python3crystax', 'python3'] - opt_depends = ['openssl','sqlite3'] - - patches = ['patches/Python-{version}-xcompile.patch', - 'patches/Python-{version}-ctypes-disable-wchar.patch', - 'patches/disable-modules.patch', - 'patches/fix-locale.patch', + conflicts = ['python3crystax', 'python3', 'python2legacy'] + + patches = [ + # new 2.7.15 patches + # ('patches/fix-api-minor-than-21.patch', + # is_api_lt(21)), # Todo: this should be tested + 'patches/fix-missing-extensions.patch', + 'patches/fix-filesystem-default-encoding.patch', 'patches/fix-gethostbyaddr.patch', - 'patches/fix-setup-flags.patch', - 'patches/fix-filesystemdefaultencoding.patch', - 'patches/fix-termios.patch', - 'patches/custom-loader.patch', - 'patches/verbose-compilation.patch', - 'patches/fix-remove-corefoundation.patch', - 'patches/fix-dynamic-lookup.patch', - 'patches/fix-dlfcn.patch', - 'patches/parsetuple.patch', - 'patches/ctypes-find-library-updated.patch', - ('patches/fix-configure-darwin.patch', is_darwin), - ('patches/fix-distutils-darwin.patch', is_darwin), - ('patches/fix-ftime-removal.patch', is_api_gt(19)), - ('patches/disable-openpty.patch', check_all(is_api_lt(21), is_ndk('crystax')))] + 'patches/fix-posix-declarations.patch', + 'patches/fix-pwd-gecos.patch'] - from_crystax = False + configure_args = ('--host={android_host}', + '--build={android_build}', + '--enable-shared', + '--disable-ipv6', + '--disable-toolbox-glue', + '--disable-framework', + 'ac_cv_file__dev_ptmx=yes', + 'ac_cv_file__dev_ptc=no', + '--without-ensurepip', + 'ac_cv_little_endian_double=yes', + 'ac_cv_header_langinfo_h=no', + '--prefix={prefix}', + '--exec-prefix={exec_prefix}') - def build_arch(self, arch): + compiled_extension = '.pyo' - if not exists(join(self.get_build_dir(arch.arch), 'libpython2.7.so')): - self.do_python_build(arch) + def prebuild_arch(self, arch): + super(Python2Recipe, self).prebuild_arch(arch) + patch_mark = join(self.get_build_dir(arch.arch), '.openssl-patched') + if 'openssl' in self.ctx.recipe_build_order and not exists(patch_mark): + self.apply_patch(join('patches', 'enable-openssl.patch'), arch.arch) + shprint(sh.touch, patch_mark) - if not exists(self.ctx.get_python_install_dir()): - shprint(sh.cp, '-a', join(self.get_build_dir(arch.arch), 'python-install'), - self.ctx.get_python_install_dir()) + def set_libs_flags(self, env, arch): + env = super(Python2Recipe, self).set_libs_flags(env, arch) + if 'libffi' in self.ctx.recipe_build_order: + # For python2 we need to tell configure that we want to use our + # compiled libffi, this step is not necessary for python3. + self.configure_args += ('--with-system-ffi',) - # This should be safe to run every time - info('Copying hostpython binary to targetpython folder') - shprint(sh.cp, self.ctx.hostpython, - join(self.ctx.get_python_install_dir(), 'bin', 'python.host')) - self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host') - - if not exists(join(self.ctx.get_libs_dir(arch.arch), 'libpython2.7.so')): - shprint(sh.cp, join(self.get_build_dir(arch.arch), 'libpython2.7.so'), self.ctx.get_libs_dir(arch.arch)) - - - # # if exists(join(self.get_build_dir(arch.arch), 'libpython2.7.so')): - # if exists(join(self.ctx.libs_dir, 'libpython2.7.so')): - # info('libpython2.7.so already exists, skipping python build.') - # if not exists(join(self.ctx.get_python_install_dir(), 'libpython2.7.so')): - # info('Copying python-install to dist-dependent location') - # shprint(sh.cp, '-a', 'python-install', self.ctx.get_python_install_dir()) - # self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host') - - # return - - def do_python_build(self, arch): - - hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx) - shprint(sh.cp, self.ctx.hostpython, self.get_build_dir(arch.arch)) - shprint(sh.cp, self.ctx.hostpgen, self.get_build_dir(arch.arch)) - hostpython = join(self.get_build_dir(arch.arch), 'hostpython') - hostpgen = join(self.get_build_dir(arch.arch), 'hostpython') - - with current_directory(self.get_build_dir(arch.arch)): - - - hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx) - shprint(sh.cp, join(hostpython_recipe.get_recipe_dir(), 'Setup'), 'Modules') - - env = arch.get_env() - - # AND: Should probably move these to get_recipe_env for - # neatness, but the whole recipe needs tidying along these - # lines - env['HOSTARCH'] = 'arm-eabi' - env['BUILDARCH'] = shprint(sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0] - env['CFLAGS'] = ' '.join([env['CFLAGS'], '-DNO_MALLINFO']) - - # TODO need to add a should_build that checks if optional - # dependencies have changed (possibly in a generic way) - if 'openssl' in self.ctx.recipe_build_order: - r = Recipe.get_recipe('openssl', self.ctx) - openssl_build_dir = r.get_build_dir(arch.arch) - setuplocal = join('Modules', 'Setup.local') - shprint(sh.cp, join(self.get_recipe_dir(), 'Setup.local-ssl'), setuplocal) - shprint(sh.sed, '-i.backup', 's#^SSL=.*#SSL={}#'.format(openssl_build_dir), setuplocal) - env['OPENSSL_VERSION'] = r.version - - if 'sqlite3' in self.ctx.recipe_build_order: - # Include sqlite3 in python2 build - r = Recipe.get_recipe('sqlite3', self.ctx) - i = ' -I' + r.get_build_dir(arch.arch) - l = ' -L' + r.get_lib_dir(arch) + ' -lsqlite3' - # Insert or append to env - f = 'CPPFLAGS' - env[f] = env[f] + i if f in env else i - f = 'LDFLAGS' - env[f] = env[f] + l if f in env else l - - configure = sh.Command('./configure') - # AND: OFLAG isn't actually set, should it be? - shprint(configure, - '--host={}'.format(env['HOSTARCH']), - '--build={}'.format(env['BUILDARCH']), - # 'OPT={}'.format(env['OFLAG']), - '--prefix={}'.format(realpath('./python-install')), - '--enable-shared', - '--disable-toolbox-glue', - '--disable-framework', - _env=env) - - # AND: tito left this comment in the original source. It's still true! - # FIXME, the first time, we got a error at: - # python$EXE ../../Tools/scripts/h2py.py -i '(u_long)' /usr/include/netinet/in.h - # /home/tito/code/python-for-android/build/python/Python-2.7.2/python: 1: Syntax error: word unexpected (expecting ")") - # because at this time, python is arm, not x86. even that, why /usr/include/netinet/in.h is used ? - # check if we can avoid this part. - - make = sh.Command(env['MAKE'].split(' ')[0]) - print('First install (expected to fail...') - try: - shprint(make, '-j5', 'install', 'HOSTPYTHON={}'.format(hostpython), - 'HOSTPGEN={}'.format(hostpgen), - 'CROSS_COMPILE_TARGET=yes', - 'INSTSONAME=libpython2.7.so', - _env=env) - except sh.ErrorReturnCode_2: - print('First python2 make failed. This is expected, trying again.') - - - print('Second install (expected to work)') - shprint(sh.touch, 'python.exe', 'python') - shprint(make, '-j5', 'install', 'HOSTPYTHON={}'.format(hostpython), - 'HOSTPGEN={}'.format(hostpgen), - 'CROSS_COMPILE_TARGET=yes', - 'INSTSONAME=libpython2.7.so', - _env=env) - - if is_darwin(): - shprint(sh.cp, join(self.get_recipe_dir(), 'patches', '_scproxy.py'), - join('python-install', 'Lib')) - shprint(sh.cp, join(self.get_recipe_dir(), 'patches', '_scproxy.py'), - join('python-install', 'lib', 'python2.7')) - - # reduce python - for dir_name in ('test', join('json', 'tests'), 'lib-tk', - join('sqlite3', 'test'), join('unittest, test'), - join('lib2to3', 'tests'), join('bsddb', 'tests'), - join('distutils', 'tests'), join('email', 'test'), - 'curses'): - shprint(sh.rm, '-rf', join('python-install', - 'lib', 'python2.7', dir_name)) - - - # info('Copying python-install to dist-dependent location') - # shprint(sh.cp, '-a', 'python-install', self.ctx.get_python_install_dir()) - - # print('Copying hostpython binary to targetpython folder') - # shprint(sh.cp, self.ctx.hostpython, - # join(self.ctx.get_python_install_dir(), 'bin', 'python.host')) - # self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host') - - - - # print('python2 build done, exiting for debug') - # exit(1) + if 'openssl' in self.ctx.recipe_build_order: + recipe = Recipe.get_recipe('openssl', self.ctx) + openssl_build = recipe.get_build_dir(arch.arch) + env['OPENSSL_BUILD'] = openssl_build + env['OPENSSL_VERSION'] = recipe.version + return env recipe = Python2Recipe() diff --git a/p4a/pythonforandroid/recipes/python2/patches/enable-openssl.patch b/p4a/pythonforandroid/recipes/python2/patches/enable-openssl.patch new file mode 100644 index 00000000..490e065c --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/enable-openssl.patch @@ -0,0 +1,46 @@ +--- Python-2.7.15.orig/setup.py 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/setup.py 2018-07-05 11:08:57.305125432 +0200 +@@ -812,18 +840,15 @@ class PyBuildExt(build_ext): + '/usr/local/ssl/include', + '/usr/contrib/ssl/include/' + ] +- ssl_incs = find_file('openssl/ssl.h', inc_dirs, +- search_for_ssl_incs_in +- ) ++ ssl_incs = [ ++ os.path.join(os.environ["OPENSSL_BUILD"], 'include'), ++ os.path.join(os.environ["OPENSSL_BUILD"], 'include', 'openssl')] + if ssl_incs is not None: + krb5_h = find_file('krb5.h', inc_dirs, + ['/usr/kerberos/include']) + if krb5_h: + ssl_incs += krb5_h +- ssl_libs = find_library_file(self.compiler, 'ssl',lib_dirs, +- ['/usr/local/ssl/lib', +- '/usr/contrib/ssl/lib/' +- ] ) ++ ssl_libs = [os.environ["OPENSSL_BUILD"]] + + if (ssl_incs is not None and + ssl_libs is not None): +@@ -841,8 +866,8 @@ class PyBuildExt(build_ext): + '^\s*#\s*define\s+OPENSSL_VERSION_NUMBER\s+(0x[0-9a-fA-F]+)' ) + + # look for the openssl version header on the compiler search path. +- opensslv_h = find_file('openssl/opensslv.h', [], +- inc_dirs + search_for_ssl_incs_in) ++ opensslv_h = [os.path.join(os.environ["OPENSSL_BUILD"], 'include'), ++ os.path.join(os.environ["OPENSSL_BUILD"], 'include', 'openssl')] + if opensslv_h: + name = os.path.join(opensslv_h[0], 'openssl/opensslv.h') + if host_platform == 'darwin' and is_macosx_sdk_path(name): +@@ -859,8 +884,7 @@ class PyBuildExt(build_ext): + + min_openssl_ver = 0x00907000 + have_any_openssl = ssl_incs is not None and ssl_libs is not None +- have_usable_openssl = (have_any_openssl and +- openssl_ver >= min_openssl_ver) ++ have_usable_openssl = (have_any_openssl and True) + + if have_any_openssl: + if have_usable_openssl: diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-api-minor-than-21.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-api-minor-than-21.patch new file mode 100644 index 00000000..73dfc981 --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-api-minor-than-21.patch @@ -0,0 +1,229 @@ +diff -Naurp Python-2.7.15.orig/configure.ac Python-2.7.15/configure.ac +--- Python-2.7.15.orig/configure.ac 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/configure.ac 2018-07-05 17:44:50.500985727 +0200 +@@ -1790,7 +1790,7 @@ fi + # structures (such as rlimit64) without declaring them. As a + # work-around, disable LFS on such configurations + +-use_lfs=yes ++use_lfs=no + AC_MSG_CHECKING(Solaris LFS bug) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define _LARGEFILE_SOURCE 1 +diff -Naurp Python-2.7.15.orig/Modules/mmapmodule.c Python-2.7.15/Modules/mmapmodule.c +--- Python-2.7.15.orig/Modules/mmapmodule.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/mmapmodule.c 2018-07-05 16:18:40.953035027 +0200 +@@ -78,6 +78,12 @@ my_getpagesize(void) + # define MAP_ANONYMOUS MAP_ANON + #endif + ++//PMPP API<21 ++#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 ++ extern void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ++#endif ++//PMPP API<21 ++ + static PyObject *mmap_module_error; + + typedef enum +diff -Naurp Python-2.7.15.orig/Modules/posixmodule.c Python-2.7.15/Modules/posixmodule.c +--- Python-2.7.15.orig/Modules/posixmodule.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/posixmodule.c 2018-07-05 16:20:48.933033807 +0200 +@@ -9477,6 +9477,12 @@ all_ins(PyObject *d) + #define MODNAME "posix" + #endif + ++//PMPP API<21 ++#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 ++ extern ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); ++#endif ++//PMPP API<21 ++ + PyMODINIT_FUNC + INITFUNC(void) + { +diff -Naurp Python-2.7.15.orig/Modules/signalmodule.c Python-2.7.15/Modules/signalmodule.c +--- Python-2.7.15.orig/Modules/signalmodule.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/signalmodule.c 2018-07-05 16:40:46.601022385 +0200 +@@ -32,6 +32,13 @@ + #include + #endif + ++//PMPP API<21 ++#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 ++ #define SIGRTMIN 32 ++ #define SIGRTMAX _NSIG ++#endif ++//PMPP API<21 ++ + #ifndef NSIG + # if defined(_NSIG) + # define NSIG _NSIG /* For BSD/SysV */ +diff -Naurp Python-2.7.15.orig/Modules/termios.c Python-2.7.15/Modules/termios.c +--- Python-2.7.15.orig/Modules/termios.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/termios.c 2018-07-05 16:43:16.457020956 +0200 +@@ -357,7 +357,11 @@ static struct constant { + #endif + + /* tcsetattr() constants */ ++#if defined(__ANDROID_API__) && __ANDROID_API__ > 0 ++ {"TCSANOW", TCSETS}, // https://github.com/android-ndk/ndk/issues/441 ++#else + {"TCSANOW", TCSANOW}, ++#endif + {"TCSADRAIN", TCSADRAIN}, + {"TCSAFLUSH", TCSAFLUSH}, + #ifdef TCSASOFT +diff -Naurp Python-2.7.15.orig/Objects/obmalloc.c Python-2.7.15/Objects/obmalloc.c +--- Python-2.7.15.orig/Objects/obmalloc.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Objects/obmalloc.c 2018-07-05 16:52:27.577015700 +0200 +@@ -1,5 +1,11 @@ + #include "Python.h" + ++//PMPP API<21 ++#if __ANDROID_API__ < 21 ++ extern void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ++#endif ++//PMPP API<21 ++ + #if defined(__has_feature) /* Clang */ + #if __has_feature(address_sanitizer) /* is ASAN enabled? */ + #define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS \ +############################################################### +######### ANDROID LOCALE PATCHES FOR ANDROID API < 21 ######### +############################################################### +--- Python-2.7.15.orig/Modules/_localemodule.c 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/_localemodule.c 2018-07-05 16:39:08.241023323 +0200 +@@ -170,6 +170,12 @@ PyLocale_setlocale(PyObject* self, PyObj + PyErr_SetString(Error, "invalid locale category"); + return NULL; + } ++#else ++ #ifdef __ANDROID__ ++ #if defined(__ANDROID_API__) && __ANDROID_API__ < 20 ++ return PyUnicode_FromFormat("%s", "C"); ++ #endif ++ #endif + #endif + + if (locale) { +@@ -215,7 +221,15 @@ PyLocale_localeconv(PyObject* self) + return NULL; + + /* if LC_NUMERIC is different in the C library, use saved value */ +- l = localeconv(); ++ //PMPP API<21 ++ #if defined(__ANDROID_API__) && __ANDROID_API__ < 21 ++ /* Don't even try on Android's broken locale.h. */ ++ goto failed; ++ #else ++ /* if LC_NUMERIC is different in the C library, use saved value */ ++ l = localeconv(); //PATCHED ++ #endif ++ //PMPP API<21 + + /* hopefully, the localeconv result survives the C library calls + involved herein */ +@@ -215,7 +215,11 @@ PyLocale_localeconv(PyObject* self) + return NULL; + + /* if LC_NUMERIC is different in the C library, use saved value */ ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 + l = localeconv(); ++#else ++ decimal_point = "."; ++#endif + + /* hopefully, the localeconv result survives the C library calls + involved herein */ +--- Python-2.7.15/Objects/stringlib/formatter.h.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Objects/stringlib/formatter.h 2018-12-26 11:37:08.771315390 +0100 +@@ -640,11 +640,17 @@ get_locale_info(int type, LocaleInfo *lo + { + switch (type) { + case LT_CURRENT_LOCALE: { ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 ++/* NDK version for SDK below 21 doesn't implement the whole C++11 standard in ++ the STL. locale.h header has stubs for localeconv() method, but the library ++ doesn't implement it. The closest Android SDK that implement localeconv() ++ is SDK 21*/ + struct lconv *locale_data = localeconv(); + locale_info->decimal_point = locale_data->decimal_point; + locale_info->thousands_sep = locale_data->thousands_sep; + locale_info->grouping = locale_data->grouping; + break; ++#endif + } + case LT_DEFAULT_LOCALE: + locale_info->decimal_point = "."; +--- Python-2.7.15/Objects/stringlib/localeutil.h.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Objects/stringlib/localeutil.h 2018-12-26 11:38:10.003314806 +0100 +@@ -202,9 +202,18 @@ _Py_InsertThousandsGroupingLocale(STRING + Py_ssize_t n_digits, + Py_ssize_t min_width) + { ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 ++/* NDK version for SDK below 21 doesn't implement the whole C++11 standard in ++ the STL. locale.h header has stubs for localeconv() method, but the library ++ doesn't implement it. The closest Android SDK that implement localeconv() ++ is SDK 21*/ + struct lconv *locale_data = localeconv(); + const char *grouping = locale_data->grouping; + const char *thousands_sep = locale_data->thousands_sep; ++#else ++ const char *grouping = "\3\0"; ++ const char *thousands_sep = ","; ++#endif + + return _Py_InsertThousandsGrouping(buffer, n_buffer, digits, n_digits, + min_width, grouping, thousands_sep); +--- Python-2.7.15/Python/pystrtod.c.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Python/pystrtod.c 2018-12-26 11:47:54.723309229 +0100 +@@ -126,7 +126,13 @@ _PyOS_ascii_strtod(const char *nptr, cha + { + char *fail_pos; + double val = -1.0; ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 ++/* NDK version for SDK below 21 doesn't implement the whole C++11 standard in ++ the STL. locale.h header has stubs for localeconv() method, but the library ++ doesn't implement it. The closest Android SDK that implement localeconv() ++ is SDK 21*/ + struct lconv *locale_data; ++#endif + const char *decimal_point; + size_t decimal_point_len; + const char *p, *decimal_point_pos; +@@ -138,8 +144,16 @@ _PyOS_ascii_strtod(const char *nptr, cha + + fail_pos = NULL; + ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 ++/* NDK version for SDK below 21 doesn't implement the whole C++11 standard in ++ the STL. locale.h header has stubs for localeconv() method, but the library ++ doesn't implement it. The closest Android SDK that implement localeconv() ++ is SDK 21*/ + locale_data = localeconv(); + decimal_point = locale_data->decimal_point; ++#else ++ decimal_point = "."; ++#endif + decimal_point_len = strlen(decimal_point); + + assert(decimal_point_len != 0); +@@ -375,8 +389,16 @@ PyOS_string_to_double(const char *s, + Py_LOCAL_INLINE(void) + change_decimal_from_locale_to_dot(char* buffer) + { ++#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 ++/* NDK version for SDK below 21 doesn't implement the whole C++11 standard in ++ the STL. locale.h header has stubs for localeconv() method, but the library ++ doesn't implement it. The closest Android SDK that implement localeconv() ++ is SDK 21*/ + struct lconv *locale_data = localeconv(); + const char *decimal_point = locale_data->decimal_point; ++#else ++ decimal_point = "."; ++#endif + + if (decimal_point[0] != '.' || decimal_point[1] != 0) { + size_t decimal_point_len = strlen(decimal_point); diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-filesystem-default-encoding.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-filesystem-default-encoding.patch new file mode 100644 index 00000000..7cf1a8f8 --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-filesystem-default-encoding.patch @@ -0,0 +1,11 @@ +--- Python-2.7.15.orig/Python/bltinmodule.c 2017-12-29 01:44:57.018079845 +0200 ++++ Python-2.7.15/Python/bltinmodule.c 2017-12-29 01:45:02.650079649 +0200 +@@ -22,7 +22,7 @@ + #elif defined(__APPLE__) + const char *Py_FileSystemDefaultEncoding = "utf-8"; + #else +-const char *Py_FileSystemDefaultEncoding = NULL; /* use default */ ++const char *Py_FileSystemDefaultEncoding = "utf-8"; /* use default */ + #endif + + /* Forward */ diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch index 7b04250a..a3824f43 100644 --- a/p4a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch @@ -1,9 +1,9 @@ ---- Python-2.7.2/Modules/socketmodule.c.orig 2012-01-06 01:40:09.915694810 +0100 -+++ Python-2.7.2/Modules/socketmodule.c 2012-01-06 01:40:36.967694486 +0100 -@@ -146,6 +146,9 @@ +--- Python-2.7.15/Modules/socketmodule.c.orig 2017-12-29 01:40:09.915694810 +0100 ++++ Python-2.7.15/Modules/socketmodule.c 2017-12-29 01:40:36.967694486 +0100 +@@ -156,6 +156,9 @@ On the other hand, not all Linux versions agree, so there the settings computed by the configure script are needed! */ - + +/* Android hack, same reason are what is described above */ +#undef HAVE_GETHOSTBYNAME_R + diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch new file mode 100644 index 00000000..a098b256 --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch @@ -0,0 +1,120 @@ +diff -Naurp Python-2.7.15/Modules/Setup.dist.orig Python-2.7.15/Modules/Setup.dist +--- Python-2.7.15/Modules/Setup.dist.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/Setup.dist 2018-11-17 20:40:20.153518694 +0100 +@@ -464,7 +464,7 @@ + # Andrew Kuchling's zlib module. + # This require zlib 1.1.3 (or later). + # See http://www.gzip.org/zlib/ +-#zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz ++zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz + + # Interface to the Expat XML parser + # +diff -Naurp Python-2.7.15.orig/Makefile.pre.in Python-2.7.15/Makefile.pre.in +--- Python-2.7.15.orig/Makefile.pre.in 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Makefile.pre.in 2018-11-18 00:43:58.777379280 +0100 +@@ -20,6 +20,7 @@ + + # === Variables set by makesetup === + ++MODNAMES= _MODNAMES_ + MODOBJS= _MODOBJS_ + MODLIBS= _MODLIBS_ + +diff -Naurp Python-2.7.15.orig/Modules/_ctypes/libffi/src/arm/sysv.S Python-2.7.15/Modules/_ctypes/libffi/src/arm/sysv.S +--- Python-2.7.15.orig/Modules/_ctypes/libffi/src/arm/sysv.S 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/_ctypes/libffi/src/arm/sysv.S 2018-11-17 22:28:50.925456603 +0100 +@@ -396,7 +396,7 @@ LSYM(Lbase_args): + beq LSYM(Lepilogue_vfp) + + cmp r3, #FFI_TYPE_SINT64 +- stmeqia r2, {r0, r1} ++ stmiaeq r2, {r0, r1} + beq LSYM(Lepilogue_vfp) + + cmp r3, #FFI_TYPE_FLOAT +diff -Naurp Python-2.7.15.orig/Modules/makesetup Python-2.7.15/Modules/makesetup +--- Python-2.7.15.orig/Modules/makesetup 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/makesetup 2018-11-18 00:43:10.289379743 +0100 +@@ -110,6 +110,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' | + # Rules appended by makedepend + " >$rulesf + DEFS= ++ NAMES= + MODS= + SHAREDMODS= + OBJS= +@@ -181,7 +182,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' | + *.*) echo 1>&2 "bad word $arg in $line" + exit 1;; + -u) skip=libs; libs="$libs -u";; +- [a-zA-Z_]*) mods="$mods $arg";; ++ [a-zA-Z_]*) NAMES="$NAMES $arg"; mods="$mods $arg";; + *) echo 1>&2 "bad word $arg in $line" + exit 1;; + esac +@@ -284,6 +285,7 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' | + echo "1i\\" >$sedf + str="# Generated automatically from $makepre by makesetup." + echo "$str" >>$sedf ++ echo "s%_MODNAMES_%$NAMES%" >>$sedf + echo "s%_MODOBJS_%$OBJS%" >>$sedf + echo "s%_MODLIBS_%$LIBS%" >>$sedf + echo "/Definitions added by makesetup/a$NL$NL$DEFS" >>$sedf +diff -Naurp Python-2.7.15.orig/setup.py Python-2.7.15/setup.py +--- Python-2.7.15.orig/setup.py 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/setup.py 2018-11-18 00:40:50.021381080 +0100 +@@ -217,7 +217,11 @@ class PyBuildExt(build_ext): + # Python header files + headers = [sysconfig.get_config_h_filename()] + headers += glob(os.path.join(sysconfig.get_path('include'), "*.h")) +- for ext in self.extensions[:]: ++ # The sysconfig variable built by makesetup, listing the already ++ # built modules as configured by the Setup files. ++ modnames = sysconfig.get_config_var('MODNAMES').split() ++ removed_modules = [] ++ for ext in self.extensions: + ext.sources = [ find_module_file(filename, moddirlist) + for filename in ext.sources ] + if ext.depends is not None: +@@ -231,10 +235,10 @@ class PyBuildExt(build_ext): + # platform specific include directories + ext.include_dirs.extend(incdirlist) + +- # If a module has already been built statically, +- # don't build it here +- if ext.name in sys.builtin_module_names: +- self.extensions.remove(ext) ++ # If a module has already been built by the Makefile, ++ # don't build it here. ++ if ext.name in modnames: ++ removed_modules.append(ext) + + # Parse Modules/Setup and Modules/Setup.local to figure out which + # modules are turned on in the file. +@@ -249,8 +253,9 @@ class PyBuildExt(build_ext): + input.close() + + for ext in self.extensions[:]: +- if ext.name in remove_modules: +- self.extensions.remove(ext) ++ if removed_modules: ++ self.extensions = [x for x in self.extensions if x not in ++ removed_modules] + + # When you run "make CC=altcc" or something similar, you really want + # those environment variables passed into the setup.py phase. Here's +@@ -290,6 +295,13 @@ class PyBuildExt(build_ext): + " detect_modules() for the module's name.") + print + ++ if removed_modules: ++ print("The following modules found by detect_modules() in" ++ " setup.py, have been") ++ print("built by the Makefile instead, as configured by the" ++ " Setup files:") ++ print_three_column([ext.name for ext in removed_modules]) ++ + if self.failed: + failed = self.failed[:] + print diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-posix-declarations.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-posix-declarations.patch new file mode 100644 index 00000000..c2a80499 --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-posix-declarations.patch @@ -0,0 +1,24 @@ +--- Python-2.7.15/Modules/posixmodule.c.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/posixmodule.c 2018-12-26 13:46:37.307241303 +0100 +@@ -35,6 +35,12 @@ + # include + #endif /* defined(__VMS) */ + ++/* On android API level 21, 'AT_EACCESS' is not declared although ++ * HAVE_FACCESSAT is defined. */ ++#ifdef __ANDROID__ ++#undef HAVE_FACCESSAT ++#endif ++ + #ifdef __cplusplus + extern "C" { + #endif +@@ -3991,7 +3997,7 @@ posix_openpty(PyObject *self, PyObject * + slave_fd = open(slave_name, O_RDWR | O_NOCTTY); /* open slave */ + if (slave_fd < 0) + return posix_error(); +-#if !defined(__CYGWIN__) && !defined(HAVE_DEV_PTC) ++#if !defined(__CYGWIN__) && !defined(__ANDROID__) && !defined(HAVE_DEV_PTC) + ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */ + ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm */ + #ifndef __hpux diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-pwd-gecos.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-pwd-gecos.patch new file mode 100644 index 00000000..cdc06fd4 --- /dev/null +++ b/p4a/pythonforandroid/recipes/python2/patches/fix-pwd-gecos.patch @@ -0,0 +1,89 @@ +--- Python-2.7.15/configure.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/configure 2018-12-26 12:46:08.163275913 +0100 +@@ -12177,6 +12177,32 @@ _ACEOF + + fi + ++ac_fn_c_check_member "$LINENO" "struct passwd" "pw_gecos" "ac_cv_member_struct_passwd_pw_gecos" " ++ #include ++ #include ++ ++" ++if test "x$ac_cv_member_struct_passwd_pw_gecos" = xyes; then : ++ ++cat >>confdefs.h <<_ACEOF ++#define HAVE_STRUCT_PASSWD_PW_GECOS 1 ++_ACEOF ++ ++ ++fi ++ac_fn_c_check_member "$LINENO" "struct passwd" "pw_passwd" "ac_cv_member_struct_passwd_pw_passwd" " ++ #include ++ #include ++ ++" ++if test "x$ac_cv_member_struct_passwd_pw_passwd" = xyes; then : ++ ++cat >>confdefs.h <<_ACEOF ++#define HAVE_STRUCT_PASSWD_PW_PASSWD 1 ++_ACEOF ++ ++ ++fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for time.h that defines altzone" >&5 + $as_echo_n "checking for time.h that defines altzone... " >&6; } +--- Python-2.7.15/configure.ac.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/configure.ac 2018-12-26 12:50:20.679273505 +0100 +@@ -3562,6 +3562,10 @@ AC_CHECK_MEMBERS([struct stat.st_flags]) + AC_CHECK_MEMBERS([struct stat.st_gen]) + AC_CHECK_MEMBERS([struct stat.st_birthtime]) + AC_CHECK_MEMBERS([struct stat.st_blocks]) ++AC_CHECK_MEMBERS([struct passwd.pw_gecos, struct passwd.pw_passwd], [], [], [[ ++ #include ++ #include ++]]) + + AC_MSG_CHECKING(for time.h that defines altzone) + AC_CACHE_VAL(ac_cv_header_time_altzone,[ +--- Python-2.7.15/pyconfig.h.in.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/pyconfig.h.in 2018-12-26 12:52:13.247272432 +0100 +@@ -737,6 +737,12 @@ + /* Define to 1 if you have the header file. */ + #undef HAVE_STROPTS_H + ++/* Define to 1 if `pw_gecos' is a member of `struct passwd'. */ ++#undef HAVE_STRUCT_PASSWD_PW_GECOS ++ ++/* Define to 1 if `pw_passwd' is a member of `struct passwd'. */ ++#undef HAVE_STRUCT_PASSWD_PW_PASSWD ++ + /* Define to 1 if `st_birthtime' is a member of `struct stat'. */ + #undef HAVE_STRUCT_STAT_ST_BIRTHTIME + +--- Python-2.7.15/Modules/pwdmodule.c.orig 2018-04-30 00:47:33.000000000 +0200 ++++ Python-2.7.15/Modules/pwdmodule.c 2018-12-26 12:38:47.611280115 +0100 +@@ -68,17 +68,17 @@ mkpwent(struct passwd *p) + #define SETS(i,val) sets(v, i, val) + + SETS(setIndex++, p->pw_name); +-#ifdef __VMS +- SETS(setIndex++, ""); +-#else ++#if defined(HAVE_STRUCT_PASSWD_PW_PASSWD) + SETS(setIndex++, p->pw_passwd); ++#else ++ SETS(setIndex++, ""); + #endif + PyStructSequence_SET_ITEM(v, setIndex++, _PyInt_FromUid(p->pw_uid)); + PyStructSequence_SET_ITEM(v, setIndex++, _PyInt_FromGid(p->pw_gid)); +-#ifdef __VMS +- SETS(setIndex++, ""); +-#else ++#if defined(HAVE_STRUCT_PASSWD_PW_GECOS) + SETS(setIndex++, p->pw_gecos); ++#else ++ SETS(setIndex++, ""); + #endif + SETS(setIndex++, p->pw_dir); + SETS(setIndex++, p->pw_shell); diff --git a/p4a/pythonforandroid/recipes/python2/patches/t.htm b/p4a/pythonforandroid/recipes/python2/patches/t.htm deleted file mode 100644 index ddb028ac..00000000 --- a/p4a/pythonforandroid/recipes/python2/patches/t.htm +++ /dev/null @@ -1,151 +0,0 @@ - - - - -xkcd: Legal Hacks - - - - - - - - - - - -
- -
-
-xkcd.com logo -A webcomic of romance,
sarcasm, math, and language.
-
-
-
-Preorder: Amazon, Barnes & Noble, Indie Bound, Hudson
-In other news, Space Weird Thing is delightful, and I feel surprisingly invested in @xkcdbracket's results. - -
-
-
-
-
-
- -
Legal Hacks
- -
-Legal Hacks -
- -
-Permanent link to this comic: http://xkcd.com/504/
-Image URL (for hotlinking/embedding): http://imgs.xkcd.com/comics/legal_hacks.png - -
-
-Selected Comics - -Grownups -Circuit Diagram -Angular Momentum -Self-Description -Alternative Energy Revolution - - -
- -

Warning: this comic occasionally contains strong language (which may -be unsuitable for children), unusual humor (which may be unsuitable for -adults), and advanced mathematics (which may be unsuitable for -liberal-arts majors).

-
BTC 1FhCLQK2ZXtCUQDtG98p6fVH7S6mxAsEey
We did not invent the algorithm. The algorithm consistently finds Jesus. The algorithm killed Jeeves.
The algorithm is banned in China. The algorithm is from Jersey. The algorithm constantly finds Jesus.
This is not the algorithm. This is close.
-
-

-This work is licensed under a -Creative Commons Attribution-NonCommercial 2.5 License. -

-This means you're free to copy and share these comics (but not to sell them). More details.

-
-
- - - - - \ No newline at end of file diff --git a/p4a/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg b/p4a/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg deleted file mode 100644 index 5f2f1f77..00000000 Binary files a/p4a/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg and /dev/null differ diff --git a/p4a/pythonforandroid/recipes/python2/patches/t_files/analytics.js b/p4a/pythonforandroid/recipes/python2/patches/t_files/analytics.js deleted file mode 100644 index 4cea34a1..00000000 --- a/p4a/pythonforandroid/recipes/python2/patches/t_files/analytics.js +++ /dev/null @@ -1,42 +0,0 @@ -(function(){var aa=encodeURIComponent,f=window,n=Math;function Pc(a,b){return a.href=b} -var Qc="replace",q="data",m="match",ja="port",u="createElement",id="setAttribute",da="getTime",A="split",B="location",ra="hasOwnProperty",ma="hostname",ga="search",E="protocol",Ab="href",kd="action",G="apply",p="push",h="hash",pa="test",ha="slice",r="cookie",t="indexOf",ia="defaultValue",v="name",y="length",Ga="sendBeacon",z="prototype",la="clientWidth",jd="target",C="call",na="clientHeight",F="substring",oa="navigator",H="join",I="toLowerCase";var $c=function(a){this.w=a||[]};$c[z].set=function(a){this.w[a]=!0};$c[z].encode=function(){for(var a=[],b=0;b=b[y])wc(a,b,c);else if(8192>=b[y])x(a,b,c)||wd(a,b,c)||wc(a,b,c);else throw ge("len",b[y]),new Da(b[y]);},wc=function(a,b,c){var d=ta(a+"?"+b);d.onload=d.onerror=function(){d.onload=null;d.onerror=null;c()}},wd=function(a,b,c){var d=O.XMLHttpRequest;if(!d)return!1;var e=new d;if(!("withCredentials"in e))return!1;e.open("POST", -a,!0);e.withCredentials=!0;e.setRequestHeader("Content-Type","text/plain");e.onreadystatechange=function(){4==e.readyState&&(c(),e=null)};e.send(b);return!0},x=function(a,b,c){return O[oa][Ga]?O[oa][Ga](a,b)?(c(),!0):!1:!1},ge=function(a,b,c){1<=100*n.random()||Aa("?")||(a=["t=error","_e="+a,"_v=j37","sr=1"],b&&a[p]("_f="+b),c&&a[p]("_m="+K(c[F](0,100))),a[p]("aip=1"),a[p]("z="+fe()),wc(oc()+"/collect",a[H]("&"),ua))};var Ha=function(){this.M=[]};Ha[z].add=function(a){this.M[p](a)};Ha[z].D=function(a){try{for(var b=0;b=100*R(a,Ka))throw"abort";}function Ma(a){if(Aa(P(a,Na)))throw"abort";}function Oa(){var a=M[B][E];if("http:"!=a&&"https:"!=a)throw"abort";} -function Pa(a){try{O[oa][Ga]?J(42):O.XMLHttpRequest&&"withCredentials"in new O.XMLHttpRequest&&J(40)}catch(b){}a.set(ld,Td(a),!0);a.set(Ac,R(a,Ac)+1);var c=[];Qa.map(function(b,e){if(e.F){var g=a.get(b);void 0!=g&&g!=e[ia]&&("boolean"==typeof g&&(g*=1),c[p](e.F+"="+K(""+g)))}});c[p]("z="+Bd());a.set(Ra,c[H]("&"),!0)} -function Sa(a){var b=P(a,gd)||oc()+"/collect",c=P(a,fa);!c&&a.get(Vd)&&(c="beacon");if(c){var d=P(a,Ra),e=a.get(Ia),e=e||ua;"image"==c?wc(b,d,e):"xhr"==c&&wd(b,d,e)||"beacon"==c&&x(b,d,e)||ba(b,d,e)}else ba(b,P(a,Ra),a.get(Ia));a.set(Ia,ua,!0)}function Hc(a){var b=O.gaData;b&&(b.expId&&a.set(Nc,b.expId),b.expVar&&a.set(Oc,b.expVar))}function cd(){if(O[oa]&&"preview"==O[oa].loadPurpose)throw"abort";}function yd(a){var b=O.gaDevIds;ka(b)&&0!=b[y]&&a.set("&did",b[H](","),!0)} -function vb(a){if(!a.get(Na))throw"abort";};var hd=function(){return n.round(2147483647*n.random())},Bd=function(){try{var a=new Uint32Array(1);O.crypto.getRandomValues(a);return a[0]&2147483647}catch(b){return hd()}},fe=hd;function Ta(a){var b=R(a,Ua);500<=b&&J(15);var c=P(a,Va);if("transaction"!=c&&"item"!=c){var c=R(a,Wa),d=(new Date)[da](),e=R(a,Xa);0==e&&a.set(Xa,d);e=n.round(2*(d-e)/1E3);0=c)throw"abort";a.set(Wa,--c)}a.set(Ua,++b)};var Ya=function(){this.data=new ee},Qa=new ee,Za=[];Ya[z].get=function(a){var b=$a(a),c=this[q].get(a);b&&void 0==c&&(c=ea(b[ia])?b[ia]():b[ia]);return b&&b.Z?b.Z(this,a,c):c};var P=function(a,b){var c=a.get(b);return void 0==c?"":""+c},R=function(a,b){var c=a.get(b);return void 0==c||""===c?0:1*c};Ya[z].set=function(a,b,c){if(a)if("object"==typeof a)for(var d in a)a[ra](d)&&ab(this,d,a[d],c);else ab(this,a,b,c)}; -var ab=function(a,b,c,d){if(void 0!=c)switch(b){case Na:wb[pa](c)}var e=$a(b);e&&e.o?e.o(a,b,c,d):a[q].set(b,c,d)},bb=function(a,b,c,d,e){this.name=a;this.F=b;this.Z=d;this.o=e;this.defaultValue=c},$a=function(a){var b=Qa.get(a);if(!b)for(var c=0;c=b)){var c=(new Date).getHours(),d=[Bd(),Bd(),Bd()][H](".");a=(3==b||5==b?"https:":"http:")+"//www.google-analytics.com/collect?z=br.";a+=[b,"A",c,d][H](".");var e=1!=b%3?"https:":"http:",e=e+"//www.google-analytics.com/collect?z=br.",e=e+[b,"B",c,d][H](".");7==b&&(e=e[Qc]("//www.","//ssl."));c=function(){4<=b&&6>=b?O[oa][Ga](e,""):ta(e)};Bd()%2?(ta(a),c()):(c(),ta(a))}}};function fc(){var a,b,c;if((c=(c=O[oa])?c.plugins:null)&&c[y])for(var d=0;d=c)&&(c={},Ec(c)||Fc(c))){var d=c[Eb];void 0==d||Infinity==d||isNaN(d)||(0c)a[b]=void 0},Fd=function(a){return function(b){"pageview"!=b.get(Va)||a.I||(a.I=!0,gc(b,function(b){a.send("timing",b)}))}};var hc=!1,mc=function(a){if("cookie"==P(a,ac)){var b=P(a,U),c=nd(a),d=kc(P(a,Yb)),e=lc(P(a,W)),g=1E3*R(a,Zb),ca=P(a,Na);if("auto"!=e)zc(b,c,d,e,ca,g)&&(hc=!0);else{J(32);var l;a:{c=[];e=xa()[A](".");if(4==e[y]&&(l=e[e[y]-1],parseInt(l,10)==l)){l=["none"];break a}for(l=e[y]-2;0<=l;l--)c[p](e[ha](l)[H]("."));c[p]("none");l=c}for(var k=0;k=a&&d[p]({hash:ca[0],R:e[g],O:ca})}return 0==d[y]?void 0:1==d[y]?d[0]:Zc(b,d)||Zc(c,d)||Zc(null,d)||d[0]}function Zc(a,b){var c,d;null==a?c=d=1:(c=La(a),d=La(D(a,".")?a[F](1):"."+a));for(var e=0;ed[y])){c=[];for(var e=0;e=ca[0]||0>=ca[1]?"":ca[H]("x");a.set(rb,c);a.set(tb,fc());a.set(ob,M.characterSet||M.charset);a.set(sb,b&&"function"===typeof b.javaEnabled&&b.javaEnabled()||!1);a.set(nb,(b&&(b.language||b.browserLanguage)||"")[I]());if(d&&a.get(cc)&&(b=M[B][h])){b=b[A](/[?&#]+/);d=[];for(c=0;carguments[y])){var b,c;"string"===typeof arguments[0]?(b=arguments[0],c=[][ha][C](arguments,1)):(b=arguments[0]&&arguments[0][Va],c=arguments);b&&(c=za(qc[b]||[],c),c[Va]=b,this.b.set(c,void 0,!0),this.filters.D(this.b),this.b[q].m={},je(this.b))}};var rc=function(a){if("prerender"==M.visibilityState)return!1;a();return!0};var td=/^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/,sc=function(a){if(ea(a[0]))this.u=a[0];else{var b=td.exec(a[0]);null!=b&&4==b[y]&&(this.c=b[1]||"t0",this.K=b[2]||"",this.C=b[3],this.a=[][ha][C](a,1),this.K||(this.A="create"==this.C,this.i="require"==this.C,this.g="provide"==this.C,this.ba="remove"==this.C),this.i&&(3<=this.a[y]?(this.X=this.a[1],this.W=this.a[2]):this.a[1]&&(qa(this.a[1])?this.X=this.a[1]:this.W=this.a[1])));b=a[1];a=a[2];if(!this.C)throw"abort";if(this.i&&(!qa(b)||""==b))throw"abort";if(this.g&& -(!qa(b)||""==b||!ea(a)))throw"abort";if(ud(this.c)||ud(this.K))throw"abort";if(this.g&&"t0"!=this.c)throw"abort";}};function ud(a){return 0<=a[t](".")||0<=a[t](":")};var Yd,Zd,$d;Yd=new ee;$d=new ee;Zd={ec:45,ecommerce:46,linkid:47}; -var ae=function(a){function b(a){var b=(a[ma]||"")[A](":")[0][I](),c=(a[E]||"")[I](),c=1*a[ja]||("http:"==c?80:"https:"==c?443:"");a=a.pathname||"";D(a,"/")||(a="/"+a);return[b,""+c,a]}var c=M[u]("a");Pc(c,M[B][Ab]);var d=(c[E]||"")[I](),e=b(c),g=c[ga]||"",ca=d+"//"+e[0]+(e[1]?":"+e[1]:"");D(a,"//")?a=d+a:D(a,"/")?a=ca+a:!a||D(a,"?")?a=ca+e[2]+(a||g):0>a[A]("/")[0][t](":")&&(a=ca+e[2][F](0,e[2].lastIndexOf("/"))+"/"+a);Pc(c,a);d=b(c);return{protocol:(c[E]||"")[I](),host:d[0],port:d[1],path:d[2],G:c[ga]|| -"",url:a||""}};var Z={ga:function(){Z.f=[]}};Z.ga();Z.D=function(a){var b=Z.J[G](Z,arguments),b=Z.f.concat(b);for(Z.f=[];0c;c++){var d=b[c].src;if(d&&0==d[t]("https://www.google-analytics.com/analytics")){J(33);b=!0;break a}}b=!1}b&&(Ba=!0)}Ud()|| -Ba||!Ed(new Od)||(J(36),Ba=!0);(O.gaplugins=O.gaplugins||{}).Linker=Dc;b=Dc[z];Yd.set("linker",Dc);X("decorate",b,b.ca,20);X("autoLink",b,b.S,25);Yd.set("displayfeatures",fd);Yd.set("adfeatures",fd);a=a&&a.q;ka(a)?Z.D[G](N,a):J(50)}};N.da=function(){for(var a=N.getAll(),b=0;b>21:b;return b};})(window); diff --git a/p4a/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css b/p4a/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css deleted file mode 100644 index 381a4283..00000000 --- a/p4a/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css +++ /dev/null @@ -1,191 +0,0 @@ -/* START GENERAL FORMAT */ -body{ - background-color:#96A8C8; - text-align:center; - font-size:16px; - font-variant:small-caps; - font-family:Lucida,Helvetica,sans-serif; - font-weight:500; - text-decoration: none; - position: absolute; - left: 50%; - width: 780px; - margin-left: -390px; -} -a{ - color:#96A8C8; - text-decoration:none; - font-weight:800 -} -a:hover{ - text-decoration:underline -} -img{ - border:0 -} -.box { /*any of the box layouts & white backgrounds*/ - background:white; - border-style:solid; - border-width:1.5px; - border-color:#071419; - border-radius: 12px; - -moz-border-radius: 12px; -} -/* END GENERAL FORMAT */ -/* START UPPER LAYOUT */ -#topContainer{ - width:780px; - position:relative; - overflow:hidden; -} -#topLeft{ - width:166px; - float:left; - position:relative; - text-align:left; - padding: 17px; -} -#topLeft ul { - margin: 0; - list-style-type: none; -} -#topLeft a { - color: #282B30; - font-size: 21px; - font-weight: 800; -} -#topLeft a:hover { - text-decoration: underline; -} -#bgLeft { - float: left; - left:0; - width: 200px; - bottom:0; - top: 0px; -} -#topRight { - width:560px; - padding-top:15px; - padding-bottom:15px; - padding-left:15px; - float:right; - position:relative; - text-align:left; - line-height: 150%; -} -#masthead { - display: block; -} -#slogan { - padding: 20px; - display: inline-block; - font-size: 20px; - font-style: italic; - font-weight: 800; - line-height: 120%; - vertical-align: top; -} -#bgRight { - right: 0; - float: right; - width: 572px; - bottom:0; - top: 0px; -} -.bg { /* necessary for positioning box layouts for bg */ - position:absolute; - z-index:-1; -} -/* END UPPER LAYOUT */ - -/*START MIDDLE */ -#middleContainer { - width:780px; - margin: 5px auto; - padding: 10px 0; -} - -#ctitle { - margin: 10px; - font-size: 21px; - font-weight: 800; -} - -ul.comicNav { - padding:0; - list-style-type:none; -} -ul.comicNav li { - display: inline; -} - -ul.comicNav li a { - /*background-color: #6E6E6E;*/ - background-color:#6E7B91; - color: #FFF; - border: 1.5px solid #333; - font-size: 16px; - font-weight: 600; - padding: 1.5px 12px; - margin: 0 4px; - text-decoration: none; - border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - box-shadow: 0 0 5px 0 gray; - -moz-box-shadow: 0 0 5px 0 gray; - -webkit-box-shadow: 0 0 5px 0 gray; -} - - -ul.comicNav a:hover, ul.comicNav a:focus { - background-color: #FFF; - color: #6E7B91; - box-shadow: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; -} - -.comicInfo { - font-size:12px; - font-style:italic; - font-weight:800; -} -#bottom { - margin-top:5px; - padding:25px 15px; - width:750px; -} -#comicLinks { - display: block; - margin: auto; - width: 300px; -} -#footnote { - clear: both; - font-size: 6px; - font-style: italic; - font-variant: small-caps; - font-weight: 800; - margin: 0; - padding: 0; -} -#licenseText { - display: block; - margin: auto; - width: 410px; -} - -#transcript {display: none;} - -#middleContainer { position:relative; left:50%; margin-left:-390px; } -#comic .comic { position:absolute; } -#comic .panel, #comic .cover, #comic .panel img { position:absolute; } -#comic .cover { z-index:10; } -#comic table { margin: auto; } - -@font-face { - font-family: 'xkcd-Regular'; - src: url('//xkcd.com/fonts/xkcd-Regular.eot?') format('eot'), url('//xkcd.com/fonts/xkcd-Regular.otf') format('opentype'); -} diff --git a/p4a/pythonforandroid/recipes/python2/patches/t_files/jquery.js b/p4a/pythonforandroid/recipes/python2/patches/t_files/jquery.js deleted file mode 100644 index 73f33fb3..00000000 --- a/p4a/pythonforandroid/recipes/python2/patches/t_files/jquery.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f -}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("