diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 1cacf53..ead010f 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -2,7 +2,7 @@ name: Publish Assets
on:
push:
- branches: [master]
+ branches: [master, upgrade_p4a_clean]
jobs:
build_arm64_aar:
@@ -16,20 +16,14 @@ jobs:
cp -r /root/.buildozer ~/.buildozer/
- name: setup
run: |
+ apt update
+ apt install libssl-dev zip unzip openjdk-11-jdk -y
export B_VERSION=$(cat $GITHUB_WORKSPACE/src/main/python/main.py | grep --color=never -oP '([0-9]+\.?)+')
echo "NEXUS_SIGNING_KEYRING_FILE=$GITHUB_WORKSPACE/signing2.pgp" >> $GITHUB_ENV
echo "BUILD_VERSION=${B_VERSION}" >> $GITHUB_ENV
export PATH=/usr/bin:$PATH
- wget -q 'https://eu.crystax.net/download/crystax-ndk-10.3.2-linux-x86_64.tar.xz' -P ~/.buildozer/android/
- tar -xf ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz -C ~/.buildozer/android/
- 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 $GITHUB_WORKSPACE/scripts/build-target-python.sh ~/.buildozer/android/crystax-ndk-10.3.2/build/tools/build-target-python.sh
- cp -f $GITHUB_WORKSPACE/scripts/mangled-glibc-syscalls__arm64.h ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21/arch-arm64/usr/include/crystax/bionic/libc/include/sys/mangled-glibc-syscalls.h
- cp -f $GITHUB_WORKSPACE/scripts/build-binary.mk ~/.buildozer/android/crystax-ndk-10.3.2/build/core/build-binary.mk
- rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/sources/sqlite
- cp -Rf $GITHUB_WORKSPACE/scripts/crystax-sources/sqlite ~/.buildozer/android/crystax-ndk-10.3.2/sources/sqlite
- rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz
+ wget -q 'https://dl.google.com/android/repository/android-ndk-r25b-linux.zip' -P ~/.buildozer/android/
+ unzip ~/.buildozer/android/android-ndk-r25b-linux.zip -d ~/.buildozer/android/
mv buildozer.spec.arm64.ci buildozer.spec
chmod u+x ./build-release.sh
- name: build release
@@ -77,6 +71,7 @@ jobs:
echo "NEXUS_SIGNING_KEYRING_FILE=$GITHUB_WORKSPACE/signing2.pgp" >> $GITHUB_ENV
export PATH=/usr/bin:$PATH
wget -q 'https://eu.crystax.net/download/crystax-ndk-10.3.2-linux-x86_64.tar.xz' -P ~/.buildozer/android/
+ unzip ~/
tar -xf ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz -C ~/.buildozer/android/
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
diff --git a/.gitignore b/.gitignore
index b39ffe8..0713181 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@ p4a/pythonforandroid/bootstraps/lbry/build/templates/google-services.json
p4a/*.apk
p4a/*.aar
+venv
\ No newline at end of file
diff --git a/buildozer.spec.arm64.ci b/buildozer.spec.arm64.ci
index 94797f8..2570999 100644
--- a/buildozer.spec.arm64.ci
+++ b/buildozer.spec.arm64.ci
@@ -39,8 +39,9 @@ version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma seperated e.g. requirements = sqlite3,kivy
-requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2021.5.30, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13.3, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==21.0.0, defusedxml, netifaces, aioupnp==0.0.17, asn1crypto, mock, cryptography, aiohttp==3.6.0, multidict==4.5.2, idna, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, prometheus_client==0.8.0, "git+https://github.com/lbryio/lbry-sdk@v0.112.0#egg=lbry"
-
+requirements = openssl, sqlite3, android, distro==1.4.0, pyjnius, certifi==2021.5.30, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13.3, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==21.0.0, defusedxml, netifaces, aioupnp==0.0.17, asn1crypto, mock, cryptography, aiohttp==3.6.0, multidict==4.5.2, idna, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, cffi, prometheus_client==0.8.0, "git+https://github.com/lbryio/lbry-sdk@v0.112.0#egg=lbry"
+# requirements = coincurve, "git+https://github.com/lbryio/lbry-sdk@v0.112.0#egg=lbry"
+# python3crystax, hostpython3crystax,
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
@@ -101,13 +102,15 @@ android.minapi = 21
android.sdk = 23
# (str) Android NDK version to use
-#android.ndk = 13b
+android.ndk = 25b
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
-android.ndk_path = ~/.buildozer/android/crystax-ndk-10.3.2
+# android.ndk_path = ~/.buildozer/android/crystax-ndk-10.3.2
+android.ndk_path = ~/.buildozer/android/android-ndk-r25b
+
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path = ~/.buildozer/android
diff --git a/docker/Dockerfile.python39.platform-28 b/docker/Dockerfile.python39.platform-28
index 6607f07..4ad2688 100644
--- a/docker/Dockerfile.python39.platform-28
+++ b/docker/Dockerfile.python39.platform-28
@@ -24,6 +24,7 @@ RUN wget 'https://dl.google.com/android/android-sdk_r23-linux.tgz' -P ~/.buildoz
RUN tar -xvf ~/.buildozer/android/platform/android-sdk_r23-linux.tgz -C ~/.buildozer/android/platform/ && \
mv ~/.buildozer/android/platform/android-sdk-linux ~/.buildozer/android/platform/android-sdk && \
unzip ~/.buildozer/android/platform/platform-28_r06.zip -d ~/.buildozer/android/platform/android-sdk/platforms && \
+ # android 9 (pie) comes with api level 28.
mv ~/.buildozer/android/platform/android-sdk/platforms/android-9 ~/.buildozer/android/platform/android-sdk/platforms/android-28 && \
mkdir -p ~/.buildozer/android/platform/android-sdk/build-tools && \
unzip ~/.buildozer/android/platform/build-tools_r28.0.3-linux.zip -d ~/.buildozer/android/platform/android-sdk/build-tools && \
@@ -45,3 +46,10 @@ RUN git clone https://github.com/lbryio/buildozer.git
RUN cd buildozer && python setup.py install && cd ..
CMD ["/bin/bash"]
+
+# result is
+# ./root/.buildozer/android/platform/android-sdk/[build-tools,cmdline-tools,licenses,platforms,tools]
+# ...build-tools/28.0.3/
+# ...cmdline-tools/5.0/[bin,lib,source.properties]
+# ...platforms/android-28/[]
+# ...tools/[]
diff --git a/p4a/pythonforandroid/__init__.py b/p4a/pythonforandroid/__init__.py
index 27f4493..f39a847 100644
--- a/p4a/pythonforandroid/__init__.py
+++ b/p4a/pythonforandroid/__init__.py
@@ -1,2 +1 @@
-
-__version__ = '0.5'
+__version__ = '2022.09.04'
diff --git a/p4a/pythonforandroid/androidndk.py b/p4a/pythonforandroid/androidndk.py
new file mode 100644
index 0000000..83cb355
--- /dev/null
+++ b/p4a/pythonforandroid/androidndk.py
@@ -0,0 +1,83 @@
+import sys
+import os
+
+
+class AndroidNDK:
+ """
+ This class is used to get the current NDK information.
+ """
+
+ ndk_dir = ""
+
+ def __init__(self, ndk_dir):
+ self.ndk_dir = ndk_dir
+
+ @property
+ def host_tag(self):
+ """
+ Returns the host tag for the current system.
+ Note: The host tag is ``darwin-x86_64`` even on Apple Silicon macs.
+ """
+ return f"{sys.platform}-x86_64"
+
+ @property
+ def llvm_prebuilt_dir(self):
+ return os.path.join(
+ self.ndk_dir, "toolchains", "llvm", "prebuilt", self.host_tag
+ )
+
+ @property
+ def llvm_bin_dir(self):
+ return os.path.join(self.llvm_prebuilt_dir, "bin")
+
+ @property
+ def clang(self):
+ return os.path.join(self.llvm_bin_dir, "clang")
+
+ @property
+ def clang_cxx(self):
+ return os.path.join(self.llvm_bin_dir, "clang++")
+
+ @property
+ def llvm_binutils_prefix(self):
+ return os.path.join(self.llvm_bin_dir, "llvm-")
+
+ @property
+ def llvm_ar(self):
+ return f"{self.llvm_binutils_prefix}ar"
+
+ @property
+ def llvm_ranlib(self):
+ return f"{self.llvm_binutils_prefix}ranlib"
+
+ @property
+ def llvm_objcopy(self):
+ return f"{self.llvm_binutils_prefix}objcopy"
+
+ @property
+ def llvm_objdump(self):
+ return f"{self.llvm_binutils_prefix}objdump"
+
+ @property
+ def llvm_readelf(self):
+ return f"{self.llvm_binutils_prefix}readelf"
+
+ @property
+ def llvm_strip(self):
+ return f"{self.llvm_binutils_prefix}strip"
+
+ @property
+ def sysroot(self):
+ return os.path.join(self.llvm_prebuilt_dir, "sysroot")
+
+ @property
+ def sysroot_include_dir(self):
+ return os.path.join(self.sysroot, "usr", "include")
+
+ @property
+ def sysroot_lib_dir(self):
+ return os.path.join(self.sysroot, "usr", "lib")
+
+ @property
+ def libcxx_include_dir(self):
+ return os.path.join(self.sysroot_include_dir, "c++", "v1")
diff --git a/p4a/pythonforandroid/archs.py b/p4a/pythonforandroid/archs.py
index 09ebba4..b960ca6 100644
--- a/p4a/pythonforandroid/archs.py
+++ b/p4a/pythonforandroid/archs.py
@@ -1,22 +1,46 @@
from distutils.spawn import find_executable
from os import environ
-from os.path import (exists, join, dirname, split)
-from glob import glob
+from os.path import join
+from multiprocessing import cpu_count
from pythonforandroid.recipe import Recipe
from pythonforandroid.util import BuildInterruptingException, build_platform
-class Arch(object):
-
- toolchain_prefix = None
- '''The prefix for the toolchain dir in the NDK.'''
+class Arch:
command_prefix = None
'''The prefix for NDK commands such as gcc.'''
+ arch = ""
+ '''Name of the arch such as: `armeabi-v7a`, `arm64-v8a`, `x86`...'''
+
+ arch_cflags = []
+ '''Specific arch `cflags`, expect to be overwrote in subclass if needed.'''
+
+ common_cflags = [
+ '-target {target}',
+ '-fomit-frame-pointer'
+ ]
+
+ common_cppflags = [
+ '-DANDROID',
+ '-I{ctx.ndk.sysroot_include_dir}',
+ '-I{python_includes}',
+ ]
+
+ common_ldflags = ['-L{ctx_libs_dir}']
+
+ common_ldlibs = ['-lm']
+
+ common_ldshared = [
+ '-pthread',
+ '-shared',
+ '-Wl,-O1',
+ '-Wl,-Bsymbolic-functions',
+ ]
+
def __init__(self, ctx):
- super(Arch, self).__init__()
self.ctx = ctx
# Allows injecting additional linker paths used by any recipe.
@@ -28,6 +52,14 @@ class Arch(object):
def __str__(self):
return self.arch
+ @property
+ def ndk_lib_dir(self):
+ return join(self.ctx.ndk.sysroot_lib_dir, self.command_prefix)
+
+ @property
+ def ndk_lib_dir_versioned(self):
+ return join(self.ndk_lib_dir, str(self.ctx.ndk_api))
+
@property
def include_dirs(self):
return [
@@ -38,216 +70,235 @@ class Arch(object):
@property
def target(self):
- target_data = self.command_prefix.split('-')
- return '-'.join(
- [target_data[0], 'none', target_data[1], target_data[2]])
+ # As of NDK r19, the toolchains installed by default with the
+ # NDK may be used in-place. The make_standalone_toolchain.py script
+ # is no longer needed for interfacing with arbitrary build systems.
+ # See: https://developer.android.com/ndk/guides/other_build_systems
+ return '{triplet}{ndk_api}'.format(
+ triplet=self.command_prefix, ndk_api=self.ctx.ndk_api
+ )
- def get_env(self, with_flags_in_cc=True, clang=False):
+ @property
+ def clang_exe(self):
+ """Full path of the clang compiler depending on the android's ndk
+ version used."""
+ return self.get_clang_exe()
+
+ @property
+ def clang_exe_cxx(self):
+ """Full path of the clang++ compiler depending on the android's ndk
+ version used."""
+ return self.get_clang_exe(plus_plus=True)
+
+ def get_clang_exe(self, with_target=False, plus_plus=False):
+ """Returns the full path of the clang/clang++ compiler, supports two
+ kwargs:
+
+ - `with_target`: prepend `target` to clang
+ - `plus_plus`: will return the clang++ compiler (defaults to `False`)
+ """
+ compiler = 'clang'
+ if with_target:
+ compiler = '{target}-{compiler}'.format(
+ target=self.target, compiler=compiler
+ )
+ if plus_plus:
+ compiler += '++'
+ return join(self.ctx.ndk.llvm_bin_dir, compiler)
+
+ def get_env(self, with_flags_in_cc=True):
env = {}
- 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))
+ # HOME: User's home directory
+ #
+ # Many tools including p4a store outputs in the user's home
+ # directory. This is found from the HOME environment variable
+ # and falls back to the system account database. Setting HOME
+ # can be used to globally divert these tools to use a different
+ # path. Furthermore, in containerized environments the user may
+ # not exist in the account database, so if HOME isn't set than
+ # these tools will fail.
+ if 'HOME' in environ:
+ env['HOME'] = environ['HOME']
- env['CFLAGS'] = ' '.join(cflags)
+ # CFLAGS/CXXFLAGS: the processor flags
+ env['CFLAGS'] = ' '.join(self.common_cflags).format(target=self.target)
+ if self.arch_cflags:
+ # each architecture may have has his own CFLAGS
+ env['CFLAGS'] += ' ' + ' '.join(self.arch_cflags)
+ env['CXXFLAGS'] = env['CFLAGS']
- # Link the extra global link paths first before anything else
+ # CPPFLAGS (for macros and includes)
+ env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(
+ ctx=self.ctx,
+ command_prefix=self.command_prefix,
+ python_includes=join(
+ self.ctx.get_python_install_dir(self.arch),
+ 'include/python{}'.format(self.ctx.python_recipe.version[0:3]),
+ ),
+ )
+
+ # LDFLAGS: 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
- ]) + ' '
+ env['LDFLAGS'] = (
+ ' '
+ + " ".join(
+ [
+ "-L'"
+ + link_path.replace("'", "'\"'\"'")
+ + "'" # no shlex.quote in py2
+ for link_path in self.extra_global_link_paths
+ ]
+ )
+ + ' ' + ' '.join(self.common_ldflags).format(
+ ctx_libs_dir=self.ctx.get_libs_dir(self.arch)
+ )
+ )
- 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)])
-
- if self.ctx.ndk == 'crystax':
- env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)
-
- toolchain_prefix = self.ctx.toolchain_prefix
- toolchain_version = self.ctx.toolchain_version
- command_prefix = self.command_prefix
-
- env['TOOLCHAIN_PREFIX'] = toolchain_prefix
- env['TOOLCHAIN_VERSION'] = toolchain_version
+ # LDLIBS: Library flags or names given to compilers when they are
+ # supposed to invoke the linker.
+ env['LDLIBS'] = ' '.join(self.common_ldlibs)
+ # CCACHE
ccache = ''
if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
# print('ccache found, will optimize builds')
ccache = self.ctx.ccache + ' '
env['USE_CCACHE'] = '1'
env['NDK_CCACHE'] = self.ctx.ccache
- env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})
+ env.update(
+ {k: v for k, v in environ.items() if k.startswith('CCACHE_')}
+ )
- 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'])
+ # Compiler: `CC` and `CXX` (and make sure that the compiler exists)
+ env['PATH'] = self.ctx.env['PATH']
+ cc = find_executable(self.clang_exe, path=env['PATH'])
if cc is None:
- print('Searching path are: {!r}'.format(environ['PATH']))
+ print('Searching path are: {!r}'.format(env['PATH']))
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))
+ 'installed. Exiting.'.format(self.clang_exe))
if with_flags_in_cc:
env['CC'] = '{ccache}{exe} {cflags}'.format(
- exe=exe,
+ exe=self.clang_exe,
ccache=ccache,
cflags=env['CFLAGS'])
env['CXX'] = '{ccache}{execxx} {cxxflags}'.format(
- execxx=execxx,
+ execxx=self.clang_exe_cxx,
ccache=ccache,
cxxflags=env['CXXFLAGS'])
else:
env['CC'] = '{ccache}{exe}'.format(
- exe=exe,
+ exe=self.clang_exe,
ccache=ccache)
env['CXX'] = '{ccache}{execxx}'.format(
- execxx=execxx,
+ execxx=self.clang_exe_cxx,
ccache=ccache)
- env['AR'] = '{}-ar'.format(command_prefix)
- env['RANLIB'] = '{}-ranlib'.format(command_prefix)
- env['LD'] = '{}-ld'.format(command_prefix)
- 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)
+ # Android's LLVM binutils
+ env['AR'] = self.ctx.ndk.llvm_ar
+ env['RANLIB'] = self.ctx.ndk.llvm_ranlib
+ env['STRIP'] = f'{self.ctx.ndk.llvm_strip} --strip-unneeded'
+ env['READELF'] = self.ctx.ndk.llvm_readelf
+ env['OBJCOPY'] = self.ctx.ndk.llvm_objcopy
+ env['MAKE'] = 'make -j{}'.format(str(cpu_count()))
+
+ # Android's arch/toolchain
+ env['ARCH'] = self.arch
+ env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))
+
+ # Custom linker options
+ env['LDSHARED'] = env['CC'] + ' ' + ' '.join(self.common_ldshared)
+
+ # Host python (used by some recipes)
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.{}-{}'.format(
- build_platform, self.ctx.python_recipe.major_minor_version_string)
+ 'native-build',
+ '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
+ # for reproducible builds
+ if 'SOURCE_DATE_EPOCH' in environ:
+ for k in 'LC_ALL TZ SOURCE_DATE_EPOCH PYTHONHASHSEED BUILD_DATE BUILD_TIME'.split():
+ if k in environ:
+ env[k] = environ[k]
return env
class ArchARM(Arch):
arch = "armeabi"
- toolchain_prefix = 'arm-linux-androideabi'
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]])
+ return '{triplet}{ndk_api}'.format(
+ triplet='-'.join(['armv7a', target_data[1], target_data[2]]),
+ ndk_api=self.ctx.ndk_api,
+ )
class ArchARMv7_a(ArchARM):
arch = 'armeabi-v7a'
-
- 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'))
- env['CXXFLAGS'] = env['CFLAGS']
- return env
+ arch_cflags = [
+ '-march=armv7-a',
+ '-mfloat-abi=softfp',
+ '-mfpu=vfp',
+ '-mthumb',
+ '-fPIC',
+ ]
class Archx86(Arch):
arch = 'x86'
- toolchain_prefix = 'x86'
command_prefix = 'i686-linux-android'
- platform_dir = 'arch-x86'
-
- 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']
- return env
+ arch_cflags = [
+ '-march=i686',
+ '-mssse3',
+ '-mfpmath=sse',
+ '-m32',
+ '-fPIC',
+ ]
class Archx86_64(Arch):
arch = 'x86_64'
- toolchain_prefix = 'x86_64'
command_prefix = 'x86_64-linux-android'
- platform_dir = 'arch-x86_64'
-
- 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']
- return env
+ arch_cflags = [
+ '-march=x86-64',
+ '-msse4.2',
+ '-mpopcnt',
+ '-m64',
+ '-fPIC',
+ ]
class ArchAarch_64(Arch):
arch = 'arm64-v8a'
- toolchain_prefix = 'aarch64-linux-android'
command_prefix = 'aarch64-linux-android'
- platform_dir = 'arch-arm64'
+ arch_cflags = [
+ '-march=armv8-a',
+ '-fPIC'
+ # '-I' + join(dirname(__file__), 'includes', 'arm64-v8a'),
+ ]
- 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
- env['CXXFLAGS'] += incpath
- if with_flags_in_cc:
- env['CC'] += incpath
- env['CXX'] += incpath
- return env
+ # Note: This `EXTRA_CFLAGS` below should target the commented `include`
+ # above in `arch_cflags`. The original lines were added during the Sdl2's
+ # bootstrap creation, and modified/commented during the migration to the
+ # NDK r19 build system, because it seems that we don't need it anymore,
+ # do we need them?
+ # def get_env(self, with_flags_in_cc=True):
+ # env = super().get_env(with_flags_in_cc)
+ # env['EXTRA_CFLAGS'] = self.arch_cflags[-1]
+ # return env
diff --git a/p4a/pythonforandroid/bdistapk.py b/p4a/pythonforandroid/bdistapk.py
index a27f4d1..bcf77cd 100644
--- a/p4a/pythonforandroid/bdistapk.py
+++ b/p4a/pythonforandroid/bdistapk.py
@@ -1,6 +1,4 @@
-from __future__ import print_function
from setuptools import Command
-from pythonforandroid import toolchain
import sys
from os.path import realpath, join, exists, dirname, curdir, basename, split
@@ -16,16 +14,16 @@ def argv_contains(t):
return False
-class BdistAPK(Command):
- description = 'Create an APK with python-for-android'
+class Bdist(Command):
user_options = []
+ package_type = None
def initialize_options(self):
for option in self.user_options:
setattr(self, option[0].strip('=').replace('-', '_'), None)
- option_dict = self.distribution.get_option_dict('apk')
+ option_dict = self.distribution.get_option_dict(self.package_type)
# This is a hack, we probably aren't supposed to loop through
# the option_dict so early because distutils does exactly the
@@ -34,10 +32,9 @@ class BdistAPK(Command):
for (option, (source, value)) in option_dict.items():
setattr(self, option, str(value))
-
def finalize_options(self):
- setup_options = self.distribution.get_option_dict('apk')
+ setup_options = self.distribution.get_option_dict(self.package_type)
for (option, (source, value)) in setup_options.items():
if source == 'command line':
continue
@@ -70,16 +67,15 @@ class BdistAPK(Command):
sys.argv.append('--version={}'.format(version))
if not argv_contains('--arch'):
- arch = 'arm64-v8a'
+ arch = 'armeabi-v7a'
self.arch = arch
sys.argv.append('--arch={}'.format(arch))
def run(self):
-
self.prepare_build_dir()
- from pythonforandroid.toolchain import main
- sys.argv[1] = 'apk'
+ from pythonforandroid.entrypoints import main
+ sys.argv[1] = self.package_type
main()
def prepare_build_dir(self):
@@ -112,7 +108,7 @@ class BdistAPK(Command):
makedirs(new_dir)
print('Including {}'.format(filen))
copyfile(filen, join(bdist_dir, filen))
- if basename(filen) in ('main.py', 'main.pyo'):
+ if basename(filen) in ('main.py', 'main.pyc'):
main_py_dirs.append(filen)
# This feels ridiculous, but how else to define the main.py dir?
@@ -123,7 +119,7 @@ class BdistAPK(Command):
exit(1)
if len(main_py_dirs) > 1:
print('WARNING: Multiple main.py dirs found, using the shortest path')
- main_py_dirs.sort(key=lambda j: len(split(j)))
+ main_py_dirs = sorted(main_py_dirs, key=lambda j: len(split(j)))
if not argv_contains('--launcher'):
sys.argv.append('--private={}'.format(
@@ -131,18 +127,39 @@ class BdistAPK(Command):
)
+class BdistAPK(Bdist):
+ """distutil command handler for 'apk'."""
+ description = 'Create an APK with python-for-android'
+ package_type = 'apk'
+
+
+class BdistAAR(Bdist):
+ """distutil command handler for 'aar'."""
+ description = 'Create an AAR with python-for-android'
+ package_type = 'aar'
+
+
+class BdistAAB(Bdist):
+ """distutil command handler for 'aab'."""
+ description = 'Create an AAB with python-for-android'
+ package_type = 'aab'
+
+
def _set_user_options():
# This seems like a silly way to do things, but not sure if there's a
# better way to pass arbitrary options onwards to p4a
- user_options = [('requirements=', None, None),]
+ user_options = [('requirements=', None, None), ]
for i, arg in enumerate(sys.argv):
if arg.startswith('--'):
if ('=' in arg or
- (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):
+ (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):
user_options.append((arg[2:].split('=')[0] + '=', None, None))
else:
user_options.append((arg[2:], None, None))
BdistAPK.user_options = user_options
+ BdistAAB.user_options = user_options
+ BdistAAR.user_options = user_options
+
_set_user_options()
diff --git a/p4a/pythonforandroid/bootstrap.py b/p4a/pythonforandroid/bootstrap.py
old mode 100644
new mode 100755
index b4a9a9e..d587347
--- a/p4a/pythonforandroid/bootstrap.py
+++ b/p4a/pythonforandroid/bootstrap.py
@@ -1,20 +1,20 @@
+import functools
+import glob
+import importlib
+import os
from os.path import (join, dirname, isdir, normpath, splitext, basename)
from os import listdir, walk, sep
import sh
import shlex
-import glob
-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)
+from pythonforandroid.logger import (shprint, info, logger, debug)
+from pythonforandroid.util import (
+ current_directory, ensure_dir, temp_directory, BuildInterruptingException)
from pythonforandroid.recipe import Recipe
-def copy_files(src_root, dest_root, override=True):
+def copy_files(src_root, dest_root, override=True, symlink=False):
for root, dirnames, filenames in walk(src_root):
for filename in filenames:
subdir = normpath(root.replace(src_root, ""))
@@ -29,12 +29,44 @@ def copy_files(src_root, dest_root, override=True):
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)
+ if symlink:
+ os.symlink(src_file, dest_file)
+ else:
+ shutil.copy(src_file, dest_file)
else:
os.makedirs(dest_file)
-class Bootstrap(object):
+default_recipe_priorities = [
+ "webview", "sdl2", "service_only" # last is highest
+]
+# ^^ NOTE: these are just the default priorities if no special rules
+# apply (which you can find in the code below), so basically if no
+# known graphical lib or web lib is used - in which case service_only
+# is the most reasonable guess.
+
+
+def _cmp_bootstraps_by_priority(a, b):
+ def rank_bootstrap(bootstrap):
+ """ Returns a ranking index for each bootstrap,
+ with higher priority ranked with higher number. """
+ if bootstrap.name in default_recipe_priorities:
+ return default_recipe_priorities.index(bootstrap.name) + 1
+ return 0
+
+ # Rank bootstraps in order:
+ rank_a = rank_bootstrap(a)
+ rank_b = rank_bootstrap(b)
+ if rank_a != rank_b:
+ return (rank_b - rank_a)
+ else:
+ if a.name < b.name: # alphabetic sort for determinism
+ return -1
+ else:
+ return 1
+
+
+class Bootstrap:
'''An Android project template, containing recipe stuff for
compilation and templated fields for APK info.
'''
@@ -45,15 +77,11 @@ class Bootstrap(object):
bootstrap_dir = None
build_dir = None
- dist_dir = None
dist_name = None
distribution = None
# All bootstraps should include Python in some way:
- recipe_depends = [
- ("python2", "python2legacy", "python3", "python3crystax"),
- 'android',
- ]
+ recipe_depends = ['python3', 'android']
can_be_chosen_automatically = True
'''Determines whether the bootstrap can be chosen as one that
@@ -70,9 +98,9 @@ class Bootstrap(object):
def dist_dir(self):
'''The dist dir at which to place the finished distribution.'''
if self.distribution is None:
- warning('Tried to access {}.dist_dir, but {}.distribution '
- 'is None'.format(self, self))
- exit(1)
+ raise BuildInterruptingException(
+ 'Internal error: tried to access {}.dist_dir, but {}.distribution '
+ 'is None'.format(self, self))
return self.distribution.dist_dir
@property
@@ -84,7 +112,7 @@ class Bootstrap(object):
and optional dependencies are being used,
and returns a list of these.'''
recipes = []
- built_recipes = self.ctx.recipe_build_order
+ built_recipes = self.ctx.recipe_build_order or []
for recipe in self.recipe_depends:
if isinstance(recipe, (tuple, list)):
for alternative in recipe:
@@ -104,70 +132,102 @@ 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__
return modname.split(".", 2)[-1]
+ def get_bootstrap_dirs(self):
+ """get all bootstrap directories, following the MRO path"""
+
+ # get all bootstrap names along the __mro__, cutting off Bootstrap and object
+ classes = self.__class__.__mro__[:-2]
+ bootstrap_names = [cls.name for cls in classes] + ['common']
+ bootstrap_dirs = [
+ join(self.ctx.root_dir, 'bootstraps', bootstrap_name)
+ for bootstrap_name in reversed(bootstrap_names)
+ ]
+ return bootstrap_dirs
+
+ def _copy_in_final_files(self):
+ if self.name == "sdl2":
+ # Get the paths for copying SDL2's java source code:
+ sdl2_recipe = Recipe.get_recipe("sdl2", self.ctx)
+ sdl2_build_dir = sdl2_recipe.get_jni_dir()
+ src_dir = join(sdl2_build_dir, "SDL", "android-project",
+ "app", "src", "main", "java",
+ "org", "libsdl", "app")
+ target_dir = join(self.dist_dir, 'src', 'main', 'java', 'org',
+ 'libsdl', 'app')
+
+ # Do actual copying:
+ info('Copying in SDL2 .java files from: ' + str(src_dir))
+ if not os.path.exists(target_dir):
+ os.makedirs(target_dir)
+ copy_files(src_dir, target_dir, override=True)
+
def prepare_build_dir(self):
- '''Ensure that a build dir exists for the recipe. This same single
- dir will be used for building all different archs.'''
+ """Ensure that a build dir exists for the recipe. This same single
+ dir will be used for building all different archs."""
+ bootstrap_dirs = self.get_bootstrap_dirs()
+ # now do a cumulative copy of all bootstrap dirs
self.build_dir = self.get_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'))
- shprint(sh.mkdir, join(self.build_dir, 'src'))
- for dirn in listdir(join(self.bootstrap_dir, 'build', 'src')):
- shprint(sh.ln, '-s', join(self.bootstrap_dir, 'build', 'src', dirn),
- join(self.build_dir, 'src'))
+ for bootstrap_dir in bootstrap_dirs:
+ copy_files(join(bootstrap_dir, 'build'), self.build_dir, symlink=self.ctx.symlink_bootstrap_files)
+
with current_directory(self.build_dir):
with open('project.properties', 'w') as fileh:
fileh.write('target=android-{}'.format(self.ctx.android_api))
- def prepare_dist_dir(self, name):
+ def prepare_dist_dir(self):
ensure_dir(self.dist_dir)
- def run_distribute(self):
+ def assemble_distribution(self):
+ ''' Copies all the files into the distribution (this function is
+ overridden by the specific bootstrap classes to do this)
+ and add in the distribution info.
+ '''
+ self._copy_in_final_files()
self.distribution.save_info(self.dist_dir)
@classmethod
- def list_bootstraps(cls):
+ def all_bootstraps(cls):
'''Find all the available bootstraps and return them.'''
forbidden_dirs = ('__pycache__', 'common')
bootstraps_dir = join(dirname(__file__), 'bootstraps')
+ result = set()
for name in listdir(bootstraps_dir):
if name in forbidden_dirs:
continue
filen = join(bootstraps_dir, name)
if isdir(filen):
- yield name
+ result.add(name)
+ return result
@classmethod
- def get_bootstrap_from_recipes(cls, recipes, ctx):
- '''Returns a bootstrap whose recipe requirements do not conflict with
- the given recipes.'''
+ def get_usable_bootstraps_for_recipes(cls, recipes, ctx):
+ '''Returns all bootstrap whose recipe requirements do not conflict
+ with the given recipes, in no particular order.'''
info('Trying to find a bootstrap that matches the given recipes.')
bootstraps = [cls.get_bootstrap(name, ctx)
- for name in cls.list_bootstraps()]
- acceptable_bootstraps = []
+ for name in cls.all_bootstraps()]
+ acceptable_bootstraps = set()
+
+ # Find out which bootstraps are acceptable:
for bs in bootstraps:
if not bs.can_be_chosen_automatically:
continue
- possible_dependency_lists = expand_dependencies(bs.recipe_depends)
+ possible_dependency_lists = expand_dependencies(bs.recipe_depends, ctx)
for possible_dependencies in possible_dependency_lists:
ok = True
+ # Check if the bootstap's dependencies have an internal conflict:
for recipe in possible_dependencies:
recipe = Recipe.get_recipe(recipe, ctx)
- if any([conflict in recipes for conflict in recipe.conflicts]):
+ if any(conflict in recipes for conflict in recipe.conflicts):
ok = False
break
+ # Check if bootstrap's dependencies conflict with chosen
+ # packages:
for recipe in recipes:
try:
recipe = Recipe.get_recipe(recipe, ctx)
@@ -175,19 +235,63 @@ class Bootstrap(object):
conflicts = []
else:
conflicts = recipe.conflicts
- if any([conflict in possible_dependencies
- for conflict in conflicts]):
+ if any(conflict in possible_dependencies
+ for conflict in conflicts):
ok = False
break
if ok and bs not in acceptable_bootstraps:
- acceptable_bootstraps.append(bs)
+ acceptable_bootstraps.add(bs)
+
info('Found {} acceptable bootstraps: {}'.format(
len(acceptable_bootstraps),
[bs.name for bs in acceptable_bootstraps]))
- if acceptable_bootstraps:
- info('Using the first of these: {}'
- .format(acceptable_bootstraps[0].name))
- return acceptable_bootstraps[0]
+ return acceptable_bootstraps
+
+ @classmethod
+ def get_bootstrap_from_recipes(cls, recipes, ctx):
+ '''Picks a single recommended default bootstrap out of
+ all_usable_bootstraps_from_recipes() for the given reicpes,
+ and returns it.'''
+
+ known_web_packages = {"flask"} # to pick webview over service_only
+ recipes_with_deps_lists = expand_dependencies(recipes, ctx)
+ acceptable_bootstraps = cls.get_usable_bootstraps_for_recipes(
+ recipes, ctx
+ )
+
+ def have_dependency_in_recipes(dep):
+ for dep_list in recipes_with_deps_lists:
+ if dep in dep_list:
+ return True
+ return False
+
+ # Special rule: return SDL2 bootstrap if there's an sdl2 dep:
+ if (have_dependency_in_recipes("sdl2") and
+ "sdl2" in [b.name for b in acceptable_bootstraps]
+ ):
+ info('Using sdl2 bootstrap since it is in dependencies')
+ return cls.get_bootstrap("sdl2", ctx)
+
+ # Special rule: return "webview" if we depend on common web recipe:
+ for possible_web_dep in known_web_packages:
+ if have_dependency_in_recipes(possible_web_dep):
+ # We have a web package dep!
+ if "webview" in [b.name for b in acceptable_bootstraps]:
+ info('Using webview bootstrap since common web packages '
+ 'were found {}'.format(
+ known_web_packages.intersection(recipes)
+ ))
+ return cls.get_bootstrap("webview", ctx)
+
+ prioritized_acceptable_bootstraps = sorted(
+ list(acceptable_bootstraps),
+ key=functools.cmp_to_key(_cmp_bootstraps_by_priority)
+ )
+
+ if prioritized_acceptable_bootstraps:
+ info('Using the highest ranked/first of these: {}'
+ .format(prioritized_acceptable_bootstraps[0].name))
+ return prioritized_acceptable_bootstraps[0]
return None
@classmethod
@@ -218,15 +322,16 @@ class Bootstrap(object):
tgt_dir = join(dest_dir, arch.arch)
ensure_dir(tgt_dir)
for src_dir in src_dirs:
- for lib in glob.glob(join(src_dir, wildcard)):
- shprint(sh.cp, '-a', lib, tgt_dir)
+ libs = glob.glob(join(src_dir, wildcard))
+ if libs:
+ shprint(sh.cp, '-a', *libs, tgt_dir)
def distribute_javaclasses(self, javaclass_dir, dest_dir="src"):
'''Copy existing javaclasses from build dir to current dist dir.'''
info('Copying java files')
ensure_dir(dest_dir)
- for filename in glob.glob(javaclass_dir):
- shprint(sh.cp, '-a', filename, dest_dir)
+ filenames = glob.glob(javaclass_dir)
+ shprint(sh.cp, '-a', *filenames, dest_dir)
def distribute_aars(self, arch):
'''Process existing .aar bundles and copy to current dist dir.'''
@@ -259,24 +364,19 @@ class Bootstrap(object):
debug(" to {}".format(so_tgt_dir))
ensure_dir(so_tgt_dir)
so_files = glob.glob(join(so_src_dir, '*.so'))
- for f in so_files:
- shprint(sh.cp, '-a', f, so_tgt_dir)
+ shprint(sh.cp, '-a', *so_files, so_tgt_dir)
def strip_libraries(self, arch):
info('Stripping libraries')
- if self.ctx.python_recipe.from_crystax:
- info('Python was loaded from CrystaX, skipping strip')
- return
env = arch.get_env()
tokens = shlex.split(env['STRIP'])
strip = sh.Command(tokens[0])
+ logger.info(f'Strip Env {env["STRIP"]} strip {strip} env {env}')
if len(tokens) > 1:
strip = strip.bake(tokens[1:])
- libs_dir = join(self.dist_dir, '_python_bundle',
+ libs_dir = join(self.dist_dir, f'_python_bundle__{arch.arch}',
'_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')
@@ -301,9 +401,31 @@ class Bootstrap(object):
shprint(sh.rm, '-rf', d)
-def expand_dependencies(recipes):
+def expand_dependencies(recipes, ctx):
+ """ This function expands to lists of all different available
+ alternative recipe combinations, with the dependencies added in
+ ONLY for all the not-with-alternative recipes.
+ (So this is like the deps graph very simplified and incomplete, but
+ hopefully good enough for most basic bootstrap compatibility checks)
+ """
+
+ # Add in all the deps of recipes where there is no alternative:
+ recipes_with_deps = list(recipes)
+ for entry in recipes:
+ if not isinstance(entry, (tuple, list)) or len(entry) == 1:
+ if isinstance(entry, (tuple, list)):
+ entry = entry[0]
+ try:
+ recipe = Recipe.get_recipe(entry, ctx)
+ recipes_with_deps += recipe.depends
+ except ValueError:
+ # it's a pure python package without a recipe, so we
+ # don't know the dependencies...skipping for now
+ pass
+
+ # Split up lists by available alternatives:
recipe_lists = [[]]
- for recipe in recipes:
+ for recipe in recipes_with_deps:
if isinstance(recipe, (tuple, list)):
new_recipe_lists = []
for alternative in recipe:
@@ -313,6 +435,6 @@ def expand_dependencies(recipes):
new_recipe_lists.append(new_list)
recipe_lists = new_recipe_lists
else:
- for old_list in recipe_lists:
- old_list.append(recipe)
+ for existing_list in recipe_lists:
+ existing_list.append(recipe)
return recipe_lists
diff --git a/p4a/pythonforandroid/bootstraps/common/build/build.py b/p4a/pythonforandroid/bootstraps/common/build/build.py
index 342115e..036ad62 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/build.py
+++ b/p4a/pythonforandroid/bootstraps/common/build/build.py
@@ -1,13 +1,13 @@
-#!/usr/bin/env python2.7
-
-from __future__ import print_function
+#!/usr/bin/env python3
+from gzip import GzipFile
+import hashlib
import json
from os.path import (
dirname, join, isfile, realpath,
relpath, split, exists, basename
)
-from os import listdir, makedirs, remove
+from os import environ, listdir, makedirs, remove
import os
import shlex
import shutil
@@ -16,19 +16,20 @@ 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):
+def get_dist_info_for(key, error_if_missing=True):
try:
with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
info = json.load(fileh)
- value = str(info[key])
+ value = info[key]
except (OSError, KeyError) as e:
+ if not error_if_missing:
+ return None
print("BUILD FAILURE: Couldn't extract the key `" + key + "` " +
"from dist_info.json: " + str(e))
sys.exit(1)
@@ -39,10 +40,6 @@ 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')
@@ -57,7 +54,6 @@ else:
curdir = dirname(__file__)
PYTHON = get_hostpython()
-PYTHON_VERSION = get_python_version()
if PYTHON is not None and not exists(PYTHON):
PYTHON = None
@@ -72,29 +68,23 @@ BLACKLIST_PATTERNS = [
'~',
'*.bak',
'*.swp',
+
+ # Android artifacts
+ '*.apk',
+ '*.aab',
]
-# 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)
+DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS = 'org.kivy.android.PythonActivity'
+DEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService'
def ensure_dir(path):
@@ -154,75 +144,33 @@ def listfiles(d):
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):
+def make_tar(tfn, source_dirs, byte_compile_python=False, 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)
+ def clean(tinfo):
+ """cleaning function (for reproducible builds)"""
+ tinfo.uid = tinfo.gid = 0
+ tinfo.uname = tinfo.gname = ''
+ tinfo.mtime = 0
+ return tinfo
# 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)]
+ for fn in listfiles(sd):
+ if is_blacklist(fn):
+ continue
+ if fn.endswith('.py') and byte_compile_python:
+ fn = compile_py_file(fn, optimize_python=optimize_python)
+ files.append((fn, relpath(realpath(fn), sd)))
+ files.sort() # deterministic
# create tar.gz of thoses files
- tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
+ gf = GzipFile(tfn, 'wb', mtime=0) # deterministic
+ tf = tarfile.open(None, 'w', gf, format=tarfile.USTAR_FORMAT)
dirs = []
for fn, afn in files:
dn = dirname(afn)
@@ -238,25 +186,24 @@ def make_tar(tfn, source_dirs, ignore_path=[], optimize_python=True):
dirs.append(d)
tinfo = tarfile.TarInfo(d)
tinfo.type = tarfile.DIRTYPE
+ clean(tinfo)
tf.addfile(tinfo)
# put the file
- tf.add(fn, afn)
+ tf.add(fn, afn, filter=clean)
tf.close()
+ gf.close()
-def compile_dir(dfn, optimize_python=True):
+def compile_py_file(python_file, optimize_python=True):
'''
- Compile *.py in directory `dfn` to *.pyo
+ Compile python_file to *.pyc and return the filename of the *.pyc file.
'''
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]
+ args = [PYTHON, '-m', 'compileall', '-b', '-f', python_file]
if optimize_python:
# -OO = strip docstrings
args.insert(1, '-OO')
@@ -268,16 +215,18 @@ def compile_dir(dfn, optimize_python=True):
'error, see logs above')
exit(1)
+ return ".".join([os.path.splitext(python_file)[0], "pyc"])
+
def make_package(args):
- # If no launcher is specified, require a main.py/main.pyo:
+ # If no launcher is specified, require a main.py/main.pyc:
if (get_bootstrap_name() != "sdl" or args.launcher is None) and \
- get_bootstrap_name() != "webview":
+ get_bootstrap_name() not in ["webview", "service_library"]:
# (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
+ not exists(join(realpath(args.private), 'main.pyc'))):
+ print('''BUILD FAILURE: No main.py(c) 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.''')
@@ -286,53 +235,168 @@ main.py that loads it.''')
assets_dir = "src/main/assets"
# Delete the old assets.
- try_unlink(join(assets_dir, 'public.mp3'))
- try_unlink(join(assets_dir, 'private.mp3'))
+ shutil.rmtree(assets_dir, ignore_errors=True)
ensure_dir(assets_dir)
-
- # In order to speedup import and initial depack,
- # construct a python27.zip
- make_python_zip()
-
+ # remove 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, "window"):
+ 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).
+ use_setup_py = get_dist_info_for("use_setup_py",
+ error_if_missing=False) is True
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)
+ _temp_dirs_to_clean = []
+ try:
+ if args.private:
+ if not use_setup_py or (
+ not exists(join(args.private, "setup.py")) and
+ not exists(join(args.private, "pyproject.toml"))
+ ):
+ print('No setup.py/pyproject.toml used, copying '
+ 'full private data into .apk.')
+ tar_dirs.append(args.private)
+ else:
+ print("Copying main.py's ONLY, since other app data is "
+ "expected in site-packages.")
+ main_py_only_dir = tempfile.mkdtemp()
+ _temp_dirs_to_clean.append(main_py_only_dir)
+
+ # skip this:
+ # if exists(join(args.private, "main.pyo")):
+ # shutil.copyfile(join(args.private, "main.pyo"),
+ # join(main_py_only_dir, "main.pyo"))
+ # elif exists(join(args.private, "main.py")):
+ # shutil.copyfile(join(args.private, "main.py"),
+ # join(main_py_only_dir, "main.py"))
+ # tar_dirs.append(main_py_only_dir)
+
+ # Check all main.py files we need to copy:
+ copy_paths = ["main.py", join("service", "main.py")]
+ for copy_path in copy_paths:
+ variants = [
+ copy_path,
+ copy_path.partition(".")[0] + ".pyc",
+ ]
+ # Check in all variants with all possible endings:
+ for variant in variants:
+ if exists(join(args.private, variant)):
+ # Make sure surrounding directly exists:
+ dir_path = os.path.dirname(variant)
+ if (len(dir_path) > 0 and
+ not exists(
+ join(main_py_only_dir, dir_path)
+ )):
+ os.mkdir(join(main_py_only_dir, dir_path))
+ # Copy actual file:
+ shutil.copyfile(
+ join(args.private, variant),
+ join(main_py_only_dir, variant),
+ )
+
+ # Append directory with all main.py's to result apk paths:
+ tar_dirs.append(main_py_only_dir)
+ # if get_bootstrap_name() == "webview":
+ # for asset in listdir('webview_includes'):
+ # shutil.copy(join('webview_includes', asset), join(assets_dir, asset))
+
+ for asset in args.assets:
+ asset_src, asset_dest = asset.split(":")
+ if isfile(realpath(asset_src)):
+ ensure_dir(dirname(join(assets_dir, asset_dest)))
+ shutil.copy(realpath(asset_src), join(assets_dir, asset_dest))
+ else:
+ shutil.copytree(realpath(asset_src), join(assets_dir, asset_dest))
+
+ if args.private or args.launcher:
+ for arch in get_dist_info_for("archs"):
+ libs_dir = f"libs/{arch}"
+ make_tar(
+ join(libs_dir, "libpybundle.so"),
+ [f"_python_bundle__{arch}"],
+ byte_compile_python=args.byte_compile_python,
+ optimize_python=args.optimize_python,
+ )
+ make_tar(
+ join(assets_dir, "private.tar"),
+ tar_dirs,
+ byte_compile_python=args.byte_compile_python,
+ optimize_python=args.optimize_python,
+ )
+ finally:
+ for directory in _temp_dirs_to_clean:
+ shutil.rmtree(directory)
# Remove extra env vars tar-able directory:
shutil.rmtree(env_vars_tarpath)
# Prepare some variables for templating process
res_dir = "src/main/res"
+ res_dir_initial = "src/res_initial"
+ # make res_dir stateless
+ if exists(res_dir_initial):
+ shutil.rmtree(res_dir, ignore_errors=True)
+ shutil.copytree(res_dir_initial, res_dir)
+ else:
+ shutil.copytree(res_dir, res_dir_initial)
+
+ # Add user resouces
+ for resource in args.resources:
+ resource_src, resource_dest = resource.split(":")
+ if isfile(realpath(resource_src)):
+ ensure_dir(dirname(join(res_dir, resource_dest)))
+ shutil.copy(realpath(resource_src), join(res_dir, resource_dest))
+ else:
+ shutil.copytree(realpath(resource_src),
+ join(res_dir, resource_dest), dirs_exist_ok=True)
+
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')
+ join(res_dir, 'mipmap/icon.png')
)
+ if args.icon_fg and args.icon_bg:
+ shutil.copy(args.icon_fg, join(res_dir, 'mipmap/icon_foreground.png'))
+ shutil.copy(args.icon_bg, join(res_dir, 'mipmap/icon_background.png'))
+ with open(join(res_dir, 'mipmap-anydpi-v26/icon.xml'), "w") as fd:
+ fd.write("""
+
+
+
+
+""")
+ elif args.icon_fg or args.icon_bg:
+ print("WARNING: Received an --icon_fg or an --icon_bg argument, but not both. "
+ "Ignoring.")
+
if get_bootstrap_name() != "service_only":
- shutil.copy(
- args.presplash or default_presplash,
- join(res_dir, 'drawable/presplash.jpg')
- )
+ lottie_splashscreen = join(res_dir, 'raw/splashscreen.json')
+ if args.presplash_lottie:
+ shutil.copy(
+ 'templates/lottie.xml',
+ join(res_dir, 'layout/lottie.xml')
+ )
+ ensure_dir(join(res_dir, 'raw'))
+ shutil.copy(
+ args.presplash_lottie,
+ join(res_dir, 'raw/splashscreen.json')
+ )
+ else:
+ if exists(lottie_splashscreen):
+ remove(lottie_splashscreen)
+ remove(join(res_dir, 'layout/lottie.xml'))
+
+ 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 = []
@@ -360,17 +424,17 @@ main.py that loads it.''')
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')
+ """
+ Set version code in format (10 + minsdk + app_version)
+ Historically versioning was (arch + minsdk + app_version),
+ with arch expressed with a single digit from 6 to 9.
+ Since the multi-arch support, has been changed to 10.
+ """
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)
+ args.numeric_version = "{}{}{}".format("10", min_sdk, version_code)
if args.intent_filters:
with open(args.intent_filters) as fd:
@@ -387,6 +451,9 @@ main.py that loads it.''')
for spec in args.extra_source_dirs:
if ':' in spec:
specdir, specincludes = spec.split(':')
+ print('WARNING: Currently gradle builds only support including source '
+ 'directories, so when building using gradle all files in '
+ '{} will be included.'.format(specdir))
else:
specdir = spec
specincludes = '**'
@@ -402,6 +469,7 @@ main.py that loads it.''')
service = True
service_names = []
+ base_service_class = args.service_class_name.split('.')[-1]
for sid, spec in enumerate(args.services):
spec = spec.split(':')
name = spec[0]
@@ -426,6 +494,7 @@ main.py that loads it.''')
foreground=foreground,
sticky=sticky,
service_id=sid + 1,
+ base_service_class=base_service_class,
)
# Find the SDK directory and target API
@@ -447,19 +516,37 @@ main.py that loads it.''')
# 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.sort(key=LooseVersion)
+ 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'
+ # Copy backup rules file if specified and update the argument
+ res_xml_dir = join(res_dir, 'xml')
+ if args.backup_rules:
+ ensure_dir(res_xml_dir)
+ shutil.copy(join(args.private, args.backup_rules), res_xml_dir)
+ args.backup_rules = split(args.backup_rules)[1][:-4]
+
+ # Copy res_xml files to src/main/res/xml
+ if args.res_xmls:
+ ensure_dir(res_xml_dir)
+ for xmlpath in args.res_xmls:
+ if not os.path.exists(xmlpath):
+ xmlpath = join(args.private, xmlpath)
+ shutil.copy(xmlpath, res_xml_dir)
+
# Render out android manifest:
manifest_path = "src/main/AndroidManifest.xml"
render_args = {
"args": args,
"service": service,
"service_names": service_names,
- "android_api": android_api
+ "android_api": android_api,
+ "debug": "debug" in args.build_mode,
+ "native_services": args.native_services
}
if get_bootstrap_name() == "sdl2":
render_args["url_scheme"] = url_scheme
@@ -482,9 +569,17 @@ main.py that loads it.''')
aars=aars,
jars=jars,
android_api=android_api,
- build_tools_version=build_tools_version
+ build_tools_version=build_tools_version,
+ debug_build="debug" in args.build_mode,
+ is_library=(get_bootstrap_name() == 'service_library'),
)
+ # gradle properties
+ render(
+ 'gradle.tmpl.properties',
+ 'gradle.properties',
+ args=args)
+
# ant build templates
render(
'build.tmpl.xml',
@@ -493,9 +588,18 @@ main.py that loads it.''')
versioned_name=versioned_name)
# String resources:
+ timestamp = time.time()
+ if 'SOURCE_DATE_EPOCH' in environ:
+ # for reproducible builds
+ timestamp = int(environ['SOURCE_DATE_EPOCH'])
+ private_version = "{} {} {}".format(
+ args.version,
+ args.numeric_version,
+ timestamp
+ )
render_args = {
"args": args,
- "private_version": str(time.time())
+ "private_version": hashlib.sha1(private_version.encode()).hexdigest()
}
if get_bootstrap_name() == "sdl2":
render_args["url_scheme"] = url_scheme
@@ -527,27 +631,31 @@ main.py that loads it.''')
for patch_name in os.listdir(join('src', 'patches')):
patch_path = join('src', 'patches', patch_name)
print("Applying patch: " + str(patch_path))
+
+ # -N: insist this is FORWARD patch, don't reverse apply
+ # -p1: strip first path component
+ # -t: batch mode, don't ask questions
+ patch_command = ["patch", "-N", "-p1", "-t", "-i", 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
- ])
+ # Use a dry run to establish whether the patch is already applied.
+ # If we don't check this, the patch may be partially applied (which is bad!)
+ subprocess.check_output(patch_command + ["--dry-run"])
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)
- )
+ # Return code 1 means not all hunks could be applied, this usually
+ # means the patch is already applied.
+ print("Warning: failed to apply patch (exit code 1), "
+ "assuming it is already applied: ",
+ str(patch_path))
else:
raise e
+ else:
+ # The dry run worked, so do the real thing
+ subprocess.check_output(patch_command)
-def parse_args(args=None):
+def parse_args_and_make_package(args=None):
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
# Get the default minsdk, equal to the NDK API that this dist is built against
@@ -602,16 +710,36 @@ tools directory of the Android SDK.
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('--asset', dest='assets',
+ action="append", default=[],
+ metavar="/path/to/source:dest",
+ help='Put this in the assets folder at assets/dest')
+ ap.add_argument('--resource', dest='resources',
+ action="append", default=[],
+ metavar="/path/to/source:kind/asset",
+ help='Put this in the res folder at res/kind')
ap.add_argument('--icon', dest='icon',
help=('A png file to use as the icon for '
'the application.'))
+ ap.add_argument('--icon-fg', dest='icon_fg',
+ help=('A png file to use as the foreground of the adaptive icon '
+ 'for the application.'))
+ ap.add_argument('--icon-bg', dest='icon_bg',
+ help=('A png file to use as the background of the adaptive icon '
+ 'for the application.'))
ap.add_argument('--service', dest='services', action='append', default=[],
help='Declare a new service entrypoint: '
'NAME:PATH_TO_PY[:foreground]')
+ ap.add_argument('--native-service', dest='native_services', action='append', default=[],
+ help='Declare a new native service: '
+ 'package.name.service')
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-lottie', dest='presplash_lottie',
+ help=('A lottie (json) file to use as an animation while the '
+ 'application is loading.'))
ap.add_argument('--presplash-color',
dest='presplash_color',
default='#000000',
@@ -636,6 +764,28 @@ tools directory of the Android SDK.
'https://developer.android.com/guide/'
'topics/manifest/'
'activity-element.html'))
+
+ ap.add_argument('--enable-androidx', dest='enable_androidx',
+ action='store_true',
+ help=('Enable the AndroidX support library, '
+ 'requires api = 28 or greater'))
+ ap.add_argument('--android-entrypoint', dest='android_entrypoint',
+ default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,
+ help='Defines which java class will be used for startup, usually a subclass of PythonActivity')
+ ap.add_argument('--android-apptheme', dest='android_apptheme',
+ default='@android:style/Theme.NoTitleBar',
+ help='Defines which app theme should be selected for the main activity')
+ ap.add_argument('--add-compile-option', dest='compile_options', default=[],
+ action='append', help='add compile options to gradle.build')
+ ap.add_argument('--add-gradle-repository', dest='gradle_repositories',
+ default=[],
+ action='append',
+ help='Ddd a repository for gradle')
+ ap.add_argument('--add-packaging-option', dest='packaging_options',
+ default=[],
+ action='append',
+ help='Dndroid packaging options')
+
ap.add_argument('--wakelock', dest='wakelock', action='store_true',
help=('Indicate if the application needs the device '
'to stay on'))
@@ -647,6 +797,13 @@ tools directory of the Android SDK.
default=join(curdir, 'whitelist.txt'),
help=('Use a whitelist file to prevent blacklisting of '
'file in the final APK'))
+ ap.add_argument('--release', dest='build_mode', action='store_const',
+ const='release', default='debug',
+ help='Build your app as a non-debug release build. '
+ '(Disables gdb debugging among other things)')
+ ap.add_argument('--with-debug-symbols', dest='with_debug_symbols',
+ action='store_const', const=True, default=False,
+ help='Will keep debug symbols from `.so` files.')
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 '
@@ -674,6 +831,8 @@ tools directory of the Android SDK.
'filename containing xml. The filename should be '
'located relative to the python-for-android '
'directory'))
+ ap.add_argument('--res_xml', dest='res_xmls', action='append', default=[],
+ help='Add files to res/xml directory (for example device-filters)', nargs='+')
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',
@@ -685,8 +844,6 @@ tools directory of the Android SDK.
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.'))
@@ -698,10 +855,33 @@ tools directory of the Android SDK.
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('--backup-rules', dest='backup_rules', default='',
+ help=('Backup rules for Android Auto Backup. Argument is a '
+ 'filename containing xml. The filename should be '
+ 'located relative to the private directory containing your source code '
+ 'files (containing your main.py entrypoint). '
+ 'See https://developer.android.com/guide/topics/data/'
+ 'autobackup#IncludingFiles for more information'))
+ ap.add_argument('--no-byte-compile-python', dest='byte_compile_python',
+ action='store_false', default=True,
+ help='Skip byte compile for .py files.')
ap.add_argument('--no-optimize-python', dest='optimize_python',
action='store_false', default=True,
- help=('Whether to compile to optimised .pyo files, using -OO '
+ help=('Whether to compile to optimised .pyc files, using -OO '
'(strips docstrings and asserts)'))
+ ap.add_argument('--extra-manifest-xml', default='',
+ help=('Extra xml to write directly inside the element of'
+ 'AndroidManifest.xml'))
+ ap.add_argument('--extra-manifest-application-arguments', default='',
+ help='Extra arguments to be added to the tag of'
+ 'AndroidManifest.xml')
+ ap.add_argument('--manifest-placeholders', dest='manifest_placeholders',
+ default='[:]', help=('Inject build variables into the manifest '
+ 'via the manifestPlaceholders property'))
+ ap.add_argument('--service-class-name', dest='service_class_name', default=DEFAULT_PYTHON_SERVICE_JAVA_CLASS,
+ help='Use that parameter if you need to implement your own PythonServive Java class')
+ ap.add_argument('--activity-class-name', dest='activity_class_name', default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,
+ help='The full java class name of the main activity')
# Put together arguments, and add those from .p4a config file:
if args is None:
@@ -721,7 +901,6 @@ tools directory of the Android SDK.
_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]
@@ -751,21 +930,19 @@ tools directory of the Android SDK.
if args.permissions and isinstance(args.permissions[0], list):
args.permissions = [p for perm in args.permissions for p in perm]
+ if args.res_xmls and isinstance(args.res_xmls[0], list):
+ args.res_xmls = [x for res in args.res_xmls for x in res]
+
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')
+ python_executable = 'python2.7'
+ try:
+ subprocess.call([python_executable, '--version'])
+ except (OSError, subprocess.CalledProcessError):
+ pass
+ else:
+ PYTHON = python_executable
if args.blacklist:
with open(args.blacklist) as fd:
@@ -785,10 +962,11 @@ tools directory of the Android SDK.
'--launcher (SDL2 bootstrap only)' +
'to have something to launch inside the .apk!')
sys.exit(1)
+ print('ARGS ARGS ARGS', args)
make_package(args)
return args
if __name__ == "__main__":
- parse_args()
+ parse_args_and_make_package()
diff --git a/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
index efc019a..dd012b8 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
+++ b/p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ 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
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
diff --git a/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk
index 4a442ee..fb2b177 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk
+++ b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk
@@ -21,7 +21,3 @@ 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/common/build/jni/application/src/start.c b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
index 3429118..bad5218 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
+++ b/p4a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
@@ -15,15 +15,11 @@
#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
@@ -169,26 +165,14 @@ int main(int argc, char *argv[]) {
// 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);
- }
+ 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);
@@ -200,24 +184,11 @@ int main(int argc, char *argv[]) {
LOGP("set wchar paths...");
} else {
- // 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");
+ LOGP("_python_bundle does not exist...this not looks good, all python"
+ " recipes should have this folder, should we expect a crash soon?");
}
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
-
LOGP("Initialized python");
/* ensure threads will work.
@@ -236,34 +207,8 @@ int main(int argc, char *argv[]) {
* replace sys.path with our path
*/
PyRun_SimpleString("import sys, posix\n");
- if (dir_exists("lib")) {
- /* 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"
- " private + '/lib/python27.zip', \n"
- " private + '/lib/python2.7/', \n"
- " private + '/lib/python2.7/lib-dynload/', \n"
- " private + '/lib/python2.7/site-packages/', \n"
- " argument ]\n");
- }
char add_site_packages_dir[256];
- if (dir_exists(crystax_python_dir)) {
- snprintf(add_site_packages_dir, 256,
- "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,
@@ -281,13 +226,13 @@ int main(int argc, char *argv[]) {
PyRun_SimpleString(
"class LogFile(object):\n"
" def __init__(self):\n"
- " self.buffer = ''\n"
+ " self.__buffer = ''\n"
" def write(self, s):\n"
- " s = self.buffer + s\n"
- " lines = s.split(\"\\n\")\n"
+ " s = self.__buffer + s\n"
+ " lines = s.split('\\n')\n"
" for l in lines[:-1]:\n"
- " androidembed.log(l)\n"
- " self.buffer = lines[-1]\n"
+ " androidembed.log(l.replace('\\x00', ''))\n"
+ " self.__buffer = lines[-1]\n"
" def flush(self):\n"
" return\n"
"sys.stdout = sys.stderr = LogFile()\n"
@@ -306,14 +251,10 @@ int main(int argc, char *argv[]) {
*/
LOGP("Run user program, change dir and execute entrypoint");
- /* Get the entrypoint, search the .pyo then .py
+ /* Get the entrypoint, search the .pyc 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;
@@ -329,21 +270,17 @@ int main(int argc, char *argv[]) {
entrypoint[strlen(env_entrypoint) - 1] = '\0';
LOGP(entrypoint);
if (!file_exists(entrypoint)) {
- LOGP("Entrypoint not found (.pyc/.pyo, fallback on .py), abort");
+ LOGP("Entrypoint not found (.pyc, fallback on .py), abort");
return -1;
}
} else {
strcpy(entrypoint, env_entrypoint);
}
} else if (!strcmp(dot, ".py")) {
- /* if .py is passed, check the pyo version first */
+ /* if .py is passed, check the pyc 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)) {
@@ -353,7 +290,7 @@ int main(int argc, char *argv[]) {
strcpy(entrypoint, env_entrypoint);
}
} else {
- LOGP("Entrypoint have an invalid extension (must be .py or .pyc/.pyo), abort.");
+ LOGP("Entrypoint have an invalid extension (must be .py or .pyc), abort.");
return -1;
}
// LOGP("Entrypoint is:");
@@ -374,8 +311,7 @@ int main(int argc, char *argv[]) {
ret = 1;
PyErr_Print(); /* This exits with the right code if SystemExit. */
PyObject *f = PySys_GetObject("stdout");
- if (PyFile_WriteString(
- "\n", f)) /* python2 used Py_FlushLine, but this no longer exists */
+ if (PyFile_WriteString("\n", f))
PyErr_Clear();
}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java
new file mode 100755
index 0000000..dd10624
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class Octal {
+
+ /**
+ * Parse an octal string from a header buffer. This is used for the file
+ * permission mode value.
+ *
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The long value of the octal string.
+ */
+ public static long parseOctal(byte[] header, int offset, int length) {
+ long result = 0;
+ boolean stillPadding = true;
+
+ int end = offset + length;
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0)
+ break;
+
+ if (header[i] == (byte) ' ' || header[i] == '0') {
+ if (stillPadding)
+ continue;
+
+ if (header[i] == (byte) ' ')
+ break;
+ }
+
+ stillPadding = false;
+
+ result = ( result << 3 ) + ( header[i] - '0' );
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse an octal integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The integer value of the octal bytes.
+ */
+ public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+ int idx = length - 1;
+
+ buf[offset + idx] = 0;
+ --idx;
+ buf[offset + idx] = (byte) ' ';
+ --idx;
+
+ if (value == 0) {
+ buf[offset + idx] = (byte) '0';
+ --idx;
+ } else {
+ for (long val = value; idx >= 0 && val > 0; --idx) {
+ buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
+ val = val >> 3;
+ }
+ }
+
+ for (; idx >= 0; --idx) {
+ buf[offset + idx] = (byte) ' ';
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse the checksum octal integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The integer value of the entry's checksum.
+ */
+ public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+ getOctalBytes( value, buf, offset, length );
+ buf[offset + length - 1] = (byte) ' ';
+ buf[offset + length - 2] = 0;
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal long integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The long value of the octal bytes.
+ */
+ public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+ byte[] temp = new byte[length + 1];
+ getOctalBytes( value, temp, 0, length + 1 );
+ System.arraycopy( temp, 0, buf, offset, length );
+ return offset + length;
+ }
+
+}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
new file mode 100755
index 0000000..4611e20
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarConstants {
+ public static final int EOF_BLOCK = 1024;
+ public static final int DATA_BLOCK = 512;
+ public static final int HEADER_BLOCK = 512;
+}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
new file mode 100755
index 0000000..fe01db4
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
@@ -0,0 +1,284 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarEntry {
+ protected File file;
+ protected TarHeader header;
+
+ private TarEntry() {
+ this.file = null;
+ header = new TarHeader();
+ }
+
+ public TarEntry(File file, String entryName) {
+ this();
+ this.file = file;
+ this.extractTarHeader(entryName);
+ }
+
+ public TarEntry(byte[] headerBuf) {
+ this();
+ this.parseTarHeader(headerBuf);
+ }
+
+ /**
+ * Constructor to create an entry from an existing TarHeader object.
+ *
+ * This method is useful to add new entries programmatically (e.g. for
+ * adding files or directories that do not exist in the file system).
+ *
+ * @param header
+ *
+ */
+ public TarEntry(TarHeader header) {
+ this.file = null;
+ this.header = header;
+ }
+
+ public boolean equals(TarEntry it) {
+ return header.name.toString().equals(it.header.name.toString());
+ }
+
+ public boolean isDescendent(TarEntry desc) {
+ return desc.header.name.toString().startsWith(header.name.toString());
+ }
+
+ public TarHeader getHeader() {
+ return header;
+ }
+
+ public String getName() {
+ String name = header.name.toString();
+ if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
+ name = header.namePrefix.toString() + "/" + name;
+ }
+
+ return name;
+ }
+
+ public void setName(String name) {
+ header.name = new StringBuffer(name);
+ }
+
+ public int getUserId() {
+ return header.userId;
+ }
+
+ public void setUserId(int userId) {
+ header.userId = userId;
+ }
+
+ public int getGroupId() {
+ return header.groupId;
+ }
+
+ public void setGroupId(int groupId) {
+ header.groupId = groupId;
+ }
+
+ public String getUserName() {
+ return header.userName.toString();
+ }
+
+ public void setUserName(String userName) {
+ header.userName = new StringBuffer(userName);
+ }
+
+ public String getGroupName() {
+ return header.groupName.toString();
+ }
+
+ public void setGroupName(String groupName) {
+ header.groupName = new StringBuffer(groupName);
+ }
+
+ public void setIds(int userId, int groupId) {
+ this.setUserId(userId);
+ this.setGroupId(groupId);
+ }
+
+ public void setModTime(long time) {
+ header.modTime = time / 1000;
+ }
+
+ public void setModTime(Date time) {
+ header.modTime = time.getTime() / 1000;
+ }
+
+ public Date getModTime() {
+ return new Date(header.modTime * 1000);
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public long getSize() {
+ return header.size;
+ }
+
+ public void setSize(long size) {
+ header.size = size;
+ }
+
+ /**
+ * Checks if the org.kamrazafar.jtar entry is a directory
+ *
+ * @return
+ */
+ public boolean isDirectory() {
+ if (this.file != null)
+ return this.file.isDirectory();
+
+ if (header != null) {
+ if (header.linkFlag == TarHeader.LF_DIR)
+ return true;
+
+ if (header.name.toString().endsWith("/"))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Extract header from File
+ *
+ * @param entryName
+ */
+ public void extractTarHeader(String entryName) {
+ header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
+ }
+
+ /**
+ * Calculate checksum
+ *
+ * @param buf
+ * @return
+ */
+ public long computeCheckSum(byte[] buf) {
+ long sum = 0;
+
+ for (int i = 0; i < buf.length; ++i) {
+ sum += 255 & buf[i];
+ }
+
+ return sum;
+ }
+
+ /**
+ * Writes the header to the byte buffer
+ *
+ * @param outbuf
+ */
+ public void writeEntryHeader(byte[] outbuf) {
+ int offset = 0;
+
+ offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
+ offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
+ offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
+ offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
+
+ long size = header.size;
+
+ offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
+ offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
+
+ int csOffset = offset;
+ for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
+ outbuf[offset++] = (byte) ' ';
+
+ outbuf[offset++] = header.linkFlag;
+
+ offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
+ offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
+ offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
+ offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
+ offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
+ offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
+ offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
+
+ for (; offset < outbuf.length;)
+ outbuf[offset++] = 0;
+
+ long checkSum = this.computeCheckSum(outbuf);
+
+ Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
+ }
+
+ /**
+ * Parses the tar header to the byte buffer
+ *
+ * @param header
+ * @param bh
+ */
+ public void parseTarHeader(byte[] bh) {
+ int offset = 0;
+
+ header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+ offset += TarHeader.NAMELEN;
+
+ header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
+ offset += TarHeader.MODELEN;
+
+ header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
+ offset += TarHeader.UIDLEN;
+
+ header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
+ offset += TarHeader.GIDLEN;
+
+ header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
+ offset += TarHeader.SIZELEN;
+
+ header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
+ offset += TarHeader.MODTIMELEN;
+
+ header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
+ offset += TarHeader.CHKSUMLEN;
+
+ header.linkFlag = bh[offset++];
+
+ header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+ offset += TarHeader.NAMELEN;
+
+ header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
+ offset += TarHeader.USTAR_MAGICLEN;
+
+ header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
+ offset += TarHeader.USTAR_USER_NAMELEN;
+
+ header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
+ offset += TarHeader.USTAR_GROUP_NAMELEN;
+
+ header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
+ offset += TarHeader.USTAR_DEVLEN;
+
+ header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
+ offset += TarHeader.USTAR_DEVLEN;
+
+ header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
+ }
+}
\ No newline at end of file
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
new file mode 100755
index 0000000..b9d3a86
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
@@ -0,0 +1,243 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * Header
+ *
+ *
+ * Offset Size Field
+ * 0 100 File name
+ * 100 8 File mode
+ * 108 8 Owner's numeric user ID
+ * 116 8 Group's numeric user ID
+ * 124 12 File size in bytes
+ * 136 12 Last modification time in numeric Unix time format
+ * 148 8 Checksum for header block
+ * 156 1 Link indicator (file type)
+ * 157 100 Name of linked file
+ *
+ *
+ *
+ * File Types
+ *
+ *
+ * Value Meaning
+ * '0' Normal file
+ * (ASCII NUL) Normal file (now obsolete)
+ * '1' Hard link
+ * '2' Symbolic link
+ * '3' Character special
+ * '4' Block special
+ * '5' Directory
+ * '6' FIFO
+ * '7' Contigous
+ *
+ *
+ *
+ *
+ * Ustar header
+ *
+ *
+ * Offset Size Field
+ * 257 6 UStar indicator "ustar"
+ * 263 2 UStar version "00"
+ * 265 32 Owner user name
+ * 297 32 Owner group name
+ * 329 8 Device major number
+ * 337 8 Device minor number
+ * 345 155 Filename prefix
+ *
+ */
+
+public class TarHeader {
+
+ /*
+ * Header
+ */
+ public static final int NAMELEN = 100;
+ public static final int MODELEN = 8;
+ public static final int UIDLEN = 8;
+ public static final int GIDLEN = 8;
+ public static final int SIZELEN = 12;
+ public static final int MODTIMELEN = 12;
+ public static final int CHKSUMLEN = 8;
+ public static final byte LF_OLDNORM = 0;
+
+ /*
+ * File Types
+ */
+ public static final byte LF_NORMAL = (byte) '0';
+ public static final byte LF_LINK = (byte) '1';
+ public static final byte LF_SYMLINK = (byte) '2';
+ public static final byte LF_CHR = (byte) '3';
+ public static final byte LF_BLK = (byte) '4';
+ public static final byte LF_DIR = (byte) '5';
+ public static final byte LF_FIFO = (byte) '6';
+ public static final byte LF_CONTIG = (byte) '7';
+
+ /*
+ * Ustar header
+ */
+
+ public static final String USTAR_MAGIC = "ustar"; // POSIX
+
+ public static final int USTAR_MAGICLEN = 8;
+ public static final int USTAR_USER_NAMELEN = 32;
+ public static final int USTAR_GROUP_NAMELEN = 32;
+ public static final int USTAR_DEVLEN = 8;
+ public static final int USTAR_FILENAME_PREFIX = 155;
+
+ // Header values
+ public StringBuffer name;
+ public int mode;
+ public int userId;
+ public int groupId;
+ public long size;
+ public long modTime;
+ public int checkSum;
+ public byte linkFlag;
+ public StringBuffer linkName;
+ public StringBuffer magic; // ustar indicator and version
+ public StringBuffer userName;
+ public StringBuffer groupName;
+ public int devMajor;
+ public int devMinor;
+ public StringBuffer namePrefix;
+
+ public TarHeader() {
+ this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
+
+ this.name = new StringBuffer();
+ this.linkName = new StringBuffer();
+
+ String user = System.getProperty("user.name", "");
+
+ if (user.length() > 31)
+ user = user.substring(0, 31);
+
+ this.userId = 0;
+ this.groupId = 0;
+ this.userName = new StringBuffer(user);
+ this.groupName = new StringBuffer("");
+ this.namePrefix = new StringBuffer();
+ }
+
+ /**
+ * Parse an entry name from a header buffer.
+ *
+ * @param name
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The header's entry name.
+ */
+ public static StringBuffer parseName(byte[] header, int offset, int length) {
+ StringBuffer result = new StringBuffer(length);
+
+ int end = offset + length;
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0)
+ break;
+ result.append((char) header[i]);
+ }
+
+ return result;
+ }
+
+ /**
+ * Determine the number of bytes in an entry name.
+ *
+ * @param name
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The number of bytes in a header's entry name.
+ */
+ public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+ int i;
+
+ for (i = 0; i < length && i < name.length(); ++i) {
+ buf[offset + i] = (byte) name.charAt(i);
+ }
+
+ for (; i < length; ++i) {
+ buf[offset + i] = 0;
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Creates a new header for a file/directory entry.
+ *
+ *
+ * @param name
+ * File name
+ * @param size
+ * File size in bytes
+ * @param modTime
+ * Last modification time in numeric Unix time format
+ * @param dir
+ * Is directory
+ *
+ * @return
+ */
+ public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
+ String name = entryName;
+ name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
+
+ TarHeader header = new TarHeader();
+ header.linkName = new StringBuffer("");
+
+ if (name.length() > 100) {
+ header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
+ header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
+ } else {
+ header.name = new StringBuffer(name);
+ }
+
+ if (dir) {
+ header.mode = 040755;
+ header.linkFlag = TarHeader.LF_DIR;
+ if (header.name.charAt(header.name.length() - 1) != '/') {
+ header.name.append("/");
+ }
+ header.size = 0;
+ } else {
+ header.mode = 0100644;
+ header.linkFlag = TarHeader.LF_NORMAL;
+ header.size = size;
+ }
+
+ header.modTime = modTime;
+ header.checkSum = 0;
+ header.devMajor = 0;
+ header.devMinor = 0;
+
+ return header;
+ }
+}
\ No newline at end of file
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
new file mode 100755
index 0000000..ec50a1b
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarInputStream extends FilterInputStream {
+
+ private static final int SKIP_BUFFER_SIZE = 2048;
+ private TarEntry currentEntry;
+ private long currentFileSize;
+ private long bytesRead;
+ private boolean defaultSkip = false;
+
+ public TarInputStream(InputStream in) {
+ super(in);
+ currentFileSize = 0;
+ bytesRead = 0;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Not supported
+ *
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ }
+
+ /**
+ * Not supported
+ *
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("mark/reset not supported");
+ }
+
+ /**
+ * Read a byte
+ *
+ * @see java.io.FilterInputStream#read()
+ */
+ @Override
+ public int read() throws IOException {
+ byte[] buf = new byte[1];
+
+ int res = this.read(buf, 0, 1);
+
+ if (res != -1) {
+ return 0xFF & buf[0];
+ }
+
+ return res;
+ }
+
+ /**
+ * Checks if the bytes being read exceed the entry size and adjusts the byte
+ * array length. Updates the byte counters
+ *
+ *
+ * @see java.io.FilterInputStream#read(byte[], int, int)
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (currentEntry != null) {
+ if (currentFileSize == currentEntry.getSize()) {
+ return -1;
+ } else if ((currentEntry.getSize() - currentFileSize) < len) {
+ len = (int) (currentEntry.getSize() - currentFileSize);
+ }
+ }
+
+ int br = super.read(b, off, len);
+
+ if (br != -1) {
+ if (currentEntry != null) {
+ currentFileSize += br;
+ }
+
+ bytesRead += br;
+ }
+
+ return br;
+ }
+
+ /**
+ * Returns the next entry in the tar file
+ *
+ * @return TarEntry
+ * @throws IOException
+ */
+ public TarEntry getNextEntry() throws IOException {
+ closeCurrentEntry();
+
+ byte[] header = new byte[TarConstants.HEADER_BLOCK];
+ byte[] theader = new byte[TarConstants.HEADER_BLOCK];
+ int tr = 0;
+
+ // Read full header
+ while (tr < TarConstants.HEADER_BLOCK) {
+ int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
+
+ if (res < 0) {
+ break;
+ }
+
+ System.arraycopy(theader, 0, header, tr, res);
+ tr += res;
+ }
+
+ // Check if record is null
+ boolean eof = true;
+ for (byte b : header) {
+ if (b != 0) {
+ eof = false;
+ break;
+ }
+ }
+
+ if (!eof) {
+ currentEntry = new TarEntry(header);
+ }
+
+ return currentEntry;
+ }
+
+ /**
+ * Returns the current offset (in bytes) from the beginning of the stream.
+ * This can be used to find out at which point in a tar file an entry's content begins, for instance.
+ */
+ public long getCurrentOffset() {
+ return bytesRead;
+ }
+
+ /**
+ * Closes the current tar entry
+ *
+ * @throws IOException
+ */
+ protected void closeCurrentEntry() throws IOException {
+ if (currentEntry != null) {
+ if (currentEntry.getSize() > currentFileSize) {
+ // Not fully read, skip rest of the bytes
+ long bs = 0;
+ while (bs < currentEntry.getSize() - currentFileSize) {
+ long res = skip(currentEntry.getSize() - currentFileSize - bs);
+
+ if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
+ // I suspect file corruption
+ throw new IOException("Possible tar file corruption");
+ }
+
+ bs += res;
+ }
+ }
+
+ currentEntry = null;
+ currentFileSize = 0L;
+ skipPad();
+ }
+ }
+
+ /**
+ * Skips the pad at the end of each tar entry file content
+ *
+ * @throws IOException
+ */
+ protected void skipPad() throws IOException {
+ if (bytesRead > 0) {
+ int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
+
+ if (extra > 0) {
+ long bs = 0;
+ while (bs < TarConstants.DATA_BLOCK - extra) {
+ long res = skip(TarConstants.DATA_BLOCK - extra - bs);
+ bs += res;
+ }
+ }
+ }
+ }
+
+ /**
+ * Skips 'n' bytes on the InputStream
+ * Overrides default implementation of skip
+ *
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ if (defaultSkip) {
+ // use skip method of parent stream
+ // may not work if skip not implemented by parent
+ long bs = super.skip(n);
+ bytesRead += bs;
+
+ return bs;
+ }
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ long left = n;
+ byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
+
+ while (left > 0) {
+ int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
+ if (res < 0) {
+ break;
+ }
+ left -= res;
+ }
+
+ return n - left;
+ }
+
+ public boolean isDefaultSkip() {
+ return defaultSkip;
+ }
+
+ public void setDefaultSkip(boolean defaultSkip) {
+ this.defaultSkip = defaultSkip;
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
new file mode 100755
index 0000000..ffdfe87
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarOutputStream extends OutputStream {
+ private final OutputStream out;
+ private long bytesWritten;
+ private long currentFileSize;
+ private TarEntry currentEntry;
+
+ public TarOutputStream(OutputStream out) {
+ this.out = out;
+ bytesWritten = 0;
+ currentFileSize = 0;
+ }
+
+ public TarOutputStream(final File fout) throws FileNotFoundException {
+ this.out = new BufferedOutputStream(new FileOutputStream(fout));
+ bytesWritten = 0;
+ currentFileSize = 0;
+ }
+
+ /**
+ * Opens a file for writing.
+ */
+ public TarOutputStream(final File fout, final boolean append) throws IOException {
+ @SuppressWarnings("resource")
+ RandomAccessFile raf = new RandomAccessFile(fout, "rw");
+ final long fileSize = fout.length();
+ if (append && fileSize > TarConstants.EOF_BLOCK) {
+ raf.seek(fileSize - TarConstants.EOF_BLOCK);
+ }
+ out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
+ }
+
+ /**
+ * Appends the EOF record and closes the stream
+ *
+ * @see java.io.FilterOutputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ closeCurrentEntry();
+ write( new byte[TarConstants.EOF_BLOCK] );
+ out.close();
+ }
+ /**
+ * Writes a byte to the stream and updates byte counters
+ *
+ * @see java.io.FilterOutputStream#write(int)
+ */
+ @Override
+ public void write(int b) throws IOException {
+ out.write( b );
+ bytesWritten += 1;
+
+ if (currentEntry != null) {
+ currentFileSize += 1;
+ }
+ }
+
+ /**
+ * Checks if the bytes being written exceed the current entry size.
+ *
+ * @see java.io.FilterOutputStream#write(byte[], int, int)
+ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (currentEntry != null && !currentEntry.isDirectory()) {
+ if (currentEntry.getSize() < currentFileSize + len) {
+ throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
+ + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
+ + "] being written." );
+ }
+ }
+
+ out.write( b, off, len );
+
+ bytesWritten += len;
+
+ if (currentEntry != null) {
+ currentFileSize += len;
+ }
+ }
+
+ /**
+ * Writes the next tar entry header on the stream
+ *
+ * @param entry
+ * @throws IOException
+ */
+ public void putNextEntry(TarEntry entry) throws IOException {
+ closeCurrentEntry();
+
+ byte[] header = new byte[TarConstants.HEADER_BLOCK];
+ entry.writeEntryHeader( header );
+
+ write( header );
+
+ currentEntry = entry;
+ }
+
+ /**
+ * Closes the current tar entry
+ *
+ * @throws IOException
+ */
+ protected void closeCurrentEntry() throws IOException {
+ if (currentEntry != null) {
+ if (currentEntry.getSize() > currentFileSize) {
+ throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
+ + currentEntry.getSize() + "] has not been fully written." );
+ }
+
+ currentEntry = null;
+ currentFileSize = 0;
+
+ pad();
+ }
+ }
+
+ /**
+ * Pads the last content block
+ *
+ * @throws IOException
+ */
+ protected void pad() throws IOException {
+ if (bytesWritten > 0) {
+ int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
+
+ if (extra > 0) {
+ write( new byte[TarConstants.DATA_BLOCK - extra] );
+ }
+ }
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
new file mode 100755
index 0000000..5016576
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * @author Kamran
+ *
+ */
+public class TarUtils {
+ /**
+ * Determines the tar file size of the given folder/file path
+ *
+ * @param path
+ * @return
+ */
+ public static long calculateTarSize(File path) {
+ return tarSize(path) + TarConstants.EOF_BLOCK;
+ }
+
+ private static long tarSize(File dir) {
+ long size = 0;
+
+ if (dir.isFile()) {
+ return entrySize(dir.length());
+ } else {
+ File[] subFiles = dir.listFiles();
+
+ if (subFiles != null && subFiles.length > 0) {
+ for (File file : subFiles) {
+ if (file.isFile()) {
+ size += entrySize(file.length());
+ } else {
+ size += tarSize(file);
+ }
+ }
+ } else {
+ // Empty folder header
+ return TarConstants.HEADER_BLOCK;
+ }
+ }
+
+ return size;
+ }
+
+ private static long entrySize(long fileSize) {
+ long size = 0;
+ size += TarConstants.HEADER_BLOCK; // Header
+ size += fileSize; // File size
+
+ long extra = size % TarConstants.DATA_BLOCK;
+
+ if (extra > 0) {
+ size += (TarConstants.DATA_BLOCK - extra); // pad
+ }
+
+ return size;
+ }
+
+ public static String trim(String s, char c) {
+ StringBuffer tmp = new StringBuffer(s);
+ for (int i = 0; i < tmp.length(); i++) {
+ if (tmp.charAt(i) != c) {
+ break;
+ } else {
+ tmp.deleteCharAt(i);
+ }
+ }
+
+ for (int i = tmp.length() - 1; i >= 0; i--) {
+ if (tmp.charAt(i) != c) {
+ break;
+ } else {
+ tmp.deleteCharAt(i);
+ }
+ }
+
+ return tmp.toString();
+ }
+}
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
index 4f20fb7..dd6f307 100644
--- 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
@@ -14,10 +14,10 @@ import android.app.PendingIntent;
import android.os.Process;
import java.io.File;
-import org.kivy.android.PythonUtil;
-
-import org.renpy.android.Hardware;
-
+//imports for channel definition
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.graphics.Color;
public class PythonService extends Service implements Runnable {
@@ -33,6 +33,8 @@ public class PythonService extends Service implements Runnable {
private String serviceEntrypoint;
// Argument to pass to Python code,
private String pythonServiceArgument;
+
+
public static PythonService mService = null;
private Intent startIntent = null;
@@ -42,10 +44,6 @@ public class PythonService extends Service implements Runnable {
autoRestartService = restart;
}
- public boolean canDisplayNotification() {
- return true;
- }
-
public int startType() {
return START_NOT_STICKY;
}
@@ -64,10 +62,15 @@ public class PythonService extends Service implements Runnable {
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;
+ return startType();
+ }
+ //intent is null if OS restarts a STICKY service
+ if (intent == null) {
+ Context context = getApplicationContext();
+ intent = getThisDefaultIntent(context, "");
}
- startIntent = intent;
+ startIntent = intent;
Bundle extras = intent.getExtras();
androidPrivate = extras.getString("androidPrivate");
androidArgument = extras.getString("androidArgument");
@@ -75,28 +78,38 @@ public class PythonService extends Service implements Runnable {
pythonName = extras.getString("pythonName");
pythonHome = extras.getString("pythonHome");
pythonPath = extras.getString("pythonPath");
+ boolean serviceStartAsForeground = (
+ extras.getString("serviceStartAsForeground").equals("true")
+ );
pythonServiceArgument = extras.getString("pythonServiceArgument");
-
pythonThread = new Thread(this);
pythonThread.start();
- if (canDisplayNotification()) {
+ if (serviceStartAsForeground) {
doStartForeground(extras);
}
return startType();
}
+ protected int getServiceId() {
+ return 1;
+ }
+
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return null;
+ }
+
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) {
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notification = new Notification(
context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
try {
@@ -109,14 +122,26 @@ public class PythonService extends Service implements Runnable {
IllegalArgumentException | InvocationTargetException e) {
}
} else {
- Notification.Builder builder = new Notification.Builder(context);
+ // for android 8+ we need to create our own channel
+ // https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1
+ String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a"; //TODO: make this configurable
+ String channelName = "Background Service"; //TODO: make this configurable
+ NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,
+ NotificationManager.IMPORTANCE_NONE);
+
+ chan.setLightColor(Color.BLUE);
+ chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(chan);
+
+ Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle(serviceTitle);
builder.setContentText(serviceDescription);
builder.setContentIntent(pIntent);
builder.setSmallIcon(context.getApplicationInfo().icon);
notification = builder.build();
}
- startForeground(1, notification);
+ startForeground(getServiceId(), notification);
}
@Override
@@ -137,7 +162,10 @@ public class PythonService extends Service implements Runnable {
@Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- stopSelf();
+ //sticky servcie runtime/restart is managed by the OS. leave it running when app is closed
+ if (startType() != START_STICKY) {
+ stopSelf();
+ }
}
@Override
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
index 1f26738..2d6ca9f 100644
--- 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
@@ -1,12 +1,20 @@
package org.kivy.android;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.File;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
+import android.widget.Toast;
+
import java.util.ArrayList;
-import java.io.FilenameFilter;
import java.util.regex.Pattern;
+import org.renpy.android.AssetExtract;
public class PythonUtil {
private static final String TAG = "pythonutil";
@@ -32,21 +40,25 @@ public class PythonUtil {
protected static ArrayList getLibraries(File libsDir) {
ArrayList libsList = new ArrayList();
- addLibraryIfExists(libsList, "crystax", libsDir);
addLibraryIfExists(libsList, "sqlite3", libsDir);
addLibraryIfExists(libsList, "ffi", libsDir);
+ addLibraryIfExists(libsList, "png16", libsDir);
addLibraryIfExists(libsList, "ssl.*", libsDir);
addLibraryIfExists(libsList, "crypto.*", libsDir);
- libsList.add("python2.7");
+ addLibraryIfExists(libsList, "SDL2", libsDir);
+ addLibraryIfExists(libsList, "SDL2_image", libsDir);
+ addLibraryIfExists(libsList, "SDL2_mixer", libsDir);
+ addLibraryIfExists(libsList, "SDL2_ttf", libsDir);
libsList.add("python3.5m");
libsList.add("python3.6m");
libsList.add("python3.7m");
+ libsList.add("python3.8");
+ libsList.add("python3.9");
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)) {
@@ -61,8 +73,8 @@ public class PythonUtil {
// 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");
+ if (lib.startsWith("python3.9") && !foundPython) {
+ throw new RuntimeException("Could not load any libpythonXXX.so");
} else if (lib.startsWith("python")) {
continue;
} else {
@@ -73,5 +85,174 @@ public class PythonUtil {
}
Log.v(TAG, "Loaded everything!");
- }
+ }
+
+ public static String getAppRoot(Context ctx) {
+ String appRoot = ctx.getFilesDir().getAbsolutePath() + "/app";
+ return appRoot;
+ }
+
+ public static String getResourceString(Context ctx, String name) {
+ // Taken from org.renpy.android.ResourceManager
+ Resources res = ctx.getResources();
+ int id = res.getIdentifier(name, "string", ctx.getPackageName());
+ return res.getString(id);
+ }
+
+ /**
+ * Show an error using a toast. (Only makes sense from non-UI threads.)
+ */
+ protected static void toastError(final Activity activity, final String msg) {
+ activity.runOnUiThread(new Runnable () {
+ public void run() {
+ Toast.makeText(activity, msg, Toast.LENGTH_LONG).show();
+ }
+ });
+
+ // Wait to show the error.
+ synchronized (activity) {
+ try {
+ activity.wait(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ protected static void recursiveDelete(File f) {
+ if (f.isDirectory()) {
+ for (File r : f.listFiles()) {
+ recursiveDelete(r);
+ }
+ }
+ f.delete();
+ }
+
+ public static void unpackAsset(
+ Context ctx,
+ final String resource,
+ File target,
+ boolean cleanup_on_version_update) {
+
+ Log.v(TAG, "Unpacking " + resource + " " + target.getName());
+
+ // The version of data in memory and on disk.
+ String dataVersion = getResourceString(ctx, resource + "_version");
+ String diskVersion = null;
+
+ Log.v(TAG, "Data version is " + dataVersion);
+
+ // If no version, no unpacking is necessary.
+ if (dataVersion == null) {
+ return;
+ }
+
+ // Check the current disk version, if any.
+ String filesDir = target.getAbsolutePath();
+ String diskVersionFn = filesDir + "/" + resource + ".version";
+
+ try {
+ byte buf[] = new byte[64];
+ InputStream is = new FileInputStream(diskVersionFn);
+ int len = is.read(buf);
+ diskVersion = new String(buf, 0, len);
+ is.close();
+ } catch (Exception e) {
+ diskVersion = "";
+ }
+
+ // If the disk data is out of date, extract it and write the version file.
+ if (! dataVersion.equals(diskVersion)) {
+ Log.v(TAG, "Extracting " + resource + " assets.");
+
+ if (cleanup_on_version_update) {
+ recursiveDelete(target);
+ }
+ target.mkdirs();
+
+ AssetExtract ae = new AssetExtract(ctx);
+ if (!ae.extractTar(resource + ".tar", target.getAbsolutePath(), "private")) {
+ String msg = "Could not extract " + resource + " data.";
+ if (ctx instanceof Activity) {
+ toastError((Activity)ctx, msg);
+ } else {
+ Log.v(TAG, msg);
+ }
+ }
+
+ try {
+ // Write .nomedia.
+ new File(target, ".nomedia").createNewFile();
+
+ // Write version file.
+ FileOutputStream os = new FileOutputStream(diskVersionFn);
+ os.write(dataVersion.getBytes());
+ os.close();
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ public static void unpackPyBundle(
+ Context ctx,
+ final String resource,
+ File target,
+ boolean cleanup_on_version_update) {
+
+ Log.v(TAG, "Unpacking " + resource + " " + target.getName());
+
+ // The version of data in memory and on disk.
+ String dataVersion = getResourceString(ctx, "private_version");
+ String diskVersion = null;
+
+ Log.v(TAG, "Data version is " + dataVersion);
+
+ // If no version, no unpacking is necessary.
+ if (dataVersion == null) {
+ return;
+ }
+
+ // Check the current disk version, if any.
+ String filesDir = target.getAbsolutePath();
+ String diskVersionFn = filesDir + "/" + "libpybundle" + ".version";
+
+ try {
+ byte buf[] = new byte[64];
+ InputStream is = new FileInputStream(diskVersionFn);
+ int len = is.read(buf);
+ diskVersion = new String(buf, 0, len);
+ is.close();
+ } catch (Exception e) {
+ diskVersion = "";
+ }
+
+ if (! dataVersion.equals(diskVersion)) {
+ // If the disk data is out of date, extract it and write the version file.
+ Log.v(TAG, "Extracting " + resource + " assets.");
+
+ if (cleanup_on_version_update) {
+ recursiveDelete(target);
+ }
+ target.mkdirs();
+
+ AssetExtract ae = new AssetExtract(ctx);
+ if (!ae.extractTar(resource + ".so", target.getAbsolutePath(), "pybundle")) {
+ String msg = "Could not extract " + resource + " data.";
+ if (ctx instanceof Activity) {
+ toastError((Activity)ctx, msg);
+ } else {
+ Log.v(TAG, msg);
+ }
+ }
+
+ try {
+ // Write version file.
+ FileOutputStream os = new FileOutputStream(diskVersionFn);
+ os.write(dataVersion.getBytes());
+ os.close();
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
}
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
index 52d6424..0a5dda6 100644
--- 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
@@ -2,36 +2,34 @@
// spaces amount
package org.renpy.android;
-import java.io.*;
-
-import android.app.Activity;
+import android.content.Context;
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.OutputStream;
import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
import java.io.File;
+import java.io.FileInputStream;
import java.util.zip.GZIPInputStream;
import android.content.res.AssetManager;
-
-import org.kamranzafar.jtar.*;
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarInputStream;
public class AssetExtract {
private AssetManager mAssetManager = null;
- private Activity mActivity = null;
- public AssetExtract(Activity act) {
- mActivity = act;
- mAssetManager = act.getAssets();
+ public AssetExtract(Context context) {
+ mAssetManager = context.getAssets();
}
- public boolean extractTar(String asset, String target) {
+ public boolean extractTar(String asset, String target, String method) {
byte buf[] = new byte[1024 * 1024];
@@ -39,7 +37,12 @@ public class AssetExtract {
TarInputStream tis = null;
try {
- assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
+ if(method == "private"){
+ assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
+ } else if (method == "pybundle") {
+ assetStream = new FileInputStream(asset);
+ }
+
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
} catch (IOException e) {
Log.e("python", "opening up extract tar", e);
@@ -51,7 +54,7 @@ public class AssetExtract {
try {
entry = tis.getNextEntry();
- } catch ( java.io.IOException e ) {
+ } catch ( IOException e ) {
Log.e("python", "extracting tar", e);
return false;
}
@@ -76,8 +79,7 @@ public class AssetExtract {
try {
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
- } catch ( FileNotFoundException e ) {
- } catch ( SecurityException e ) { };
+ } catch ( FileNotFoundException | SecurityException e ) {}
if ( out == null ) {
Log.e("python", "could not open " + path);
@@ -97,7 +99,7 @@ public class AssetExtract {
out.flush();
out.close();
- } catch ( java.io.IOException e ) {
+ } catch ( IOException e ) {
Log.e("python", "extracting zip", e);
return false;
}
diff --git a/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java
new file mode 100644
index 0000000..8475762
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java
@@ -0,0 +1,279 @@
+package org.renpy.android;
+
+import android.content.Context;
+import android.os.Vibrator;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.DisplayMetrics;
+import android.view.inputmethod.InputMethodManager;
+import android.view.View;
+
+import java.util.List;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import org.kivy.android.PythonActivity;
+
+/**
+ * Methods that are expected to be called via JNI, to access the
+ * device's non-screen hardware. (For example, the vibration and
+ * accelerometer.)
+ */
+public class Hardware {
+
+ // The context.
+ static Context context;
+ static View view;
+ public static final float defaultRv[] = { 0f, 0f, 0f };
+
+ /**
+ * Vibrate for s seconds.
+ */
+ public static void vibrate(double s) {
+ Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ if (v != null) {
+ v.vibrate((int) (1000 * s));
+ }
+ }
+
+ /**
+ * Get an Overview of all Hardware Sensors of an Android Device
+ */
+ public static String getHardwareSensors() {
+ SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ List allSensors = sm.getSensorList(Sensor.TYPE_ALL);
+
+ if (allSensors != null) {
+ String resultString = "";
+ for (Sensor s : allSensors) {
+ resultString += String.format("Name=" + s.getName());
+ resultString += String.format(",Vendor=" + s.getVendor());
+ resultString += String.format(",Version=" + s.getVersion());
+ resultString += String.format(",MaximumRange=" + s.getMaximumRange());
+ // XXX MinDelay is not in the 2.2
+ //resultString += String.format(",MinDelay=" + s.getMinDelay());
+ resultString += String.format(",Power=" + s.getPower());
+ resultString += String.format(",Type=" + s.getType() + "\n");
+ }
+ return resultString;
+ }
+ return "";
+ }
+
+
+ /**
+ * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors
+ */
+ public static class generic3AxisSensor implements SensorEventListener {
+ private final SensorManager sSensorManager;
+ private final Sensor sSensor;
+ private final int sSensorType;
+ SensorEvent sSensorEvent;
+
+ public generic3AxisSensor(int sensorType) {
+ sSensorType = sensorType;
+ sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ sSensor = sSensorManager.getDefaultSensor(sSensorType);
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ public void onSensorChanged(SensorEvent event) {
+ sSensorEvent = event;
+ }
+
+ /**
+ * Enable or disable the Sensor by registering/unregistering
+ */
+ public void changeStatus(boolean enable) {
+ if (enable) {
+ sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ sSensorManager.unregisterListener(this, sSensor);
+ }
+ }
+
+ /**
+ * Read the Sensor
+ */
+ public float[] readSensor() {
+ if (sSensorEvent != null) {
+ return sSensorEvent.values;
+ } else {
+ return defaultRv;
+ }
+ }
+ }
+
+ public static generic3AxisSensor accelerometerSensor = null;
+ public static generic3AxisSensor orientationSensor = null;
+ public static generic3AxisSensor magneticFieldSensor = null;
+
+ /**
+ * functions for backward compatibility reasons
+ */
+
+ public static void accelerometerEnable(boolean enable) {
+ if ( accelerometerSensor == null )
+ accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);
+ accelerometerSensor.changeStatus(enable);
+ }
+ public static float[] accelerometerReading() {
+ if ( accelerometerSensor == null )
+ return defaultRv;
+ return (float[]) accelerometerSensor.readSensor();
+ }
+ public static void orientationSensorEnable(boolean enable) {
+ if ( orientationSensor == null )
+ orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
+ orientationSensor.changeStatus(enable);
+ }
+ public static float[] orientationSensorReading() {
+ if ( orientationSensor == null )
+ return defaultRv;
+ return (float[]) orientationSensor.readSensor();
+ }
+ public static void magneticFieldSensorEnable(boolean enable) {
+ if ( magneticFieldSensor == null )
+ magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ magneticFieldSensor.changeStatus(enable);
+ }
+ public static float[] magneticFieldSensorReading() {
+ if ( magneticFieldSensor == null )
+ return defaultRv;
+ return (float[]) magneticFieldSensor.readSensor();
+ }
+
+ static public DisplayMetrics metrics = new DisplayMetrics();
+
+ /**
+ * Get display DPI.
+ */
+ public static int getDPI() {
+ // AND: Shouldn't have to get the metrics like this every time...
+ PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ return metrics.densityDpi;
+ }
+
+ // /**
+ // * Show the soft keyboard.
+ // */
+ // public static void showKeyboard(int input_type) {
+ // //Log.i("python", "hardware.Java show_keyword " input_type);
+
+ // InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // SDLSurfaceView vw = (SDLSurfaceView) view;
+
+ // int inputType = input_type;
+
+ // if (vw.inputType != inputType){
+ // vw.inputType = inputType;
+ // imm.restartInput(view);
+ // }
+
+ // imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
+ // }
+
+ /**
+ * Hide the soft keyboard.
+ */
+ public static void hideKeyboard() {
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+
+ /**
+ * Scan WiFi networks
+ */
+ static List latestResult;
+
+ public static void enableWifiScanner()
+ {
+ IntentFilter i = new IntentFilter();
+ i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+ context.registerReceiver(new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context c, Intent i) {
+ // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs
+ WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
+ latestResult = w.getScanResults(); // Returns a of scanResults
+ }
+
+ }, i);
+
+ }
+
+ public static String scanWifi() {
+
+ // Now you can call this and it should execute the broadcastReceiver's
+ // onReceive()
+ if (latestResult != null){
+
+ String latestResultString = "";
+ for (ScanResult result : latestResult)
+ {
+ latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level);
+ }
+
+ return latestResultString;
+ }
+
+ return "";
+ }
+
+ /**
+ * network state
+ */
+
+ public static boolean network_state = false;
+
+ /**
+ * Check network state directly
+ *
+ * (only one connection can be active at a given moment, detects all network type)
+ *
+ */
+ public static boolean checkNetwork()
+ {
+ boolean state = false;
+ final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
+ if (activeNetwork != null && activeNetwork.isConnected()) {
+ state = true;
+ } else {
+ state = false;
+ }
+
+ return state;
+ }
+
+ /**
+ * To recieve network state changes
+ */
+ public static void registerNetworkCheck()
+ {
+ IntentFilter i = new IntentFilter();
+ i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ context.registerReceiver(new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context c, Intent i) {
+ network_state = checkNetwork();
+ }
+
+ }, i);
+ }
+
+}
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
index 47455ab..a170c84 100644
--- 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
@@ -1,8 +1,7 @@
/**
* 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.
+ * change. So this is the next best thing.
*/
package org.renpy.android;
diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java
index 3ed10c2..de84ac4 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java
+++ b/p4a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java
@@ -1,18 +1,11 @@
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;
+import {{ args.service_class_name }};
-public class Service{{ name|capitalize }} extends PythonService {
+public class Service{{ name|capitalize }} extends {{ base_service_class }} {
{% if sticky %}
@Override
public int startType() {
@@ -20,54 +13,35 @@ public class Service{{ name|capitalize }} extends PythonService {
}
{% 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);
+ protected int getServiceId() {
+ return {{ service_id }};
}
static public void start(Context ctx, String pythonServiceArgument) {
+ Intent intent = getDefaultIntent(ctx, pythonServiceArgument);
+ ctx.startService(intent);
+ }
+
+ static public Intent getDefaultIntent(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("serviceTitle", "{{ args.name }}");
+ intent.putExtra("serviceDescription", "{{ name|capitalize }}");
intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
intent.putExtra("pythonHome", argument);
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
- ctx.startService(intent);
+ return intent;
+ }
+
+ @Override
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return Service{{ name|capitalize }}.getDefaultIntent(ctx, pythonServiceArgument);
}
static public void stop(Context ctx) {
diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle
index 32bd091..bb00039 100644
--- a/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle
+++ b/p4a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
+ classpath 'com.android.tools.build:gradle:7.1.2'
}
}
@@ -13,23 +13,45 @@ allprojects {
repositories {
google()
jcenter()
- flatDir {
- dirs 'libs'
- }
+ {%- for repo in args.gradle_repositories %}
+ {{repo}}
+ {%- endfor %}
+ flatDir {
+ dirs 'libs'
+ }
}
}
+{% if is_library %}
+apply plugin: 'com.android.library'
+{% else %}
apply plugin: 'com.android.application'
+{% endif %}
android {
- compileSdkVersion {{ android_api }}
- buildToolsVersion '{{ build_tools_version }}'
- defaultConfig {
- minSdkVersion {{ args.min_sdk_version }}
- targetSdkVersion {{ android_api }}
- versionCode {{ args.numeric_version }}
- versionName '{{ args.version }}'
+ compileSdkVersion {{ android_api }}
+ buildToolsVersion '{{ build_tools_version }}'
+ defaultConfig {
+ minSdkVersion {{ args.min_sdk_version }}
+ targetSdkVersion {{ android_api }}
+ versionCode {{ args.numeric_version }}
+ versionName '{{ args.version }}'
+ manifestPlaceholders = {{ args.manifest_placeholders}}
+ }
+
+
+ packagingOptions {
+ jniLibs {
+ useLegacyPackaging = true
+ }
+ {% if debug_build -%}
+ doNotStrip '**/*.so'
+ {% else %}
+ exclude 'lib/**/gdbserver'
+ exclude 'lib/**/gdb.setup'
+ {%- endif %}
}
+
{% if args.sign -%}
signingConfigs {
@@ -40,41 +62,73 @@ android {
keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD")
}
}
+
{%- endif %}
- buildTypes {
- debug {
- }
- release {
- {% if args.sign -%}
- signingConfig signingConfigs.release
- {%- endif %}
- }
- }
+ {% if args.packaging_options -%}
+ packagingOptions {
+ {%- for option in args.packaging_options %}
+ {{option}}
+ {%- endfor %}
+ }
+ {%- endif %}
+
+ buildTypes {
+ debug {
+ }
+ release {
+ {% if args.sign -%}
+ signingConfig signingConfigs.release
+ {%- endif %}
+ }
+ }
compileOptions {
+ {% if args.enable_androidx %}
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ {% else %}
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
+ {% endif %}
+ {%- for option in args.compile_options %}
+ {{option}}
+ {%- endfor %}
}
sourceSets {
main {
jniLibs.srcDir 'libs'
+ java {
+
+ {%- for adir, pattern in args.extra_source_dirs -%}
+ srcDir '{{adir}}'
+ {%- endfor -%}
+
}
+ }
+ }
+
+ aaptOptions {
+ noCompress "tflite"
}
}
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 %}
+ {%- for aar in aars %}
+ implementation(name: '{{ aar }}', ext: 'aar')
+ {%- endfor -%}
+ {%- for jar in jars %}
+ implementation files('src/main/libs/{{ jar }}')
+ {%- endfor -%}
+ {%- if args.depends -%}
+ {%- for depend in args.depends %}
+ implementation '{{ depend }}'
+ {%- endfor %}
+ {%- endif %}
+ {% if args.presplash_lottie %}
+ implementation 'com.airbnb.android:lottie:3.4.0'
+ {%- endif %}
}
+
diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties b/p4a/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties
new file mode 100644
index 0000000..f99dd5a
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties
@@ -0,0 +1,4 @@
+{% if args.enable_androidx %}
+android.useAndroidX=true
+android.enableJetifier=true
+{% endif %}
\ No newline at end of file
diff --git a/p4a/pythonforandroid/bootstraps/common/build/templates/lottie.xml b/p4a/pythonforandroid/bootstraps/common/build/templates/lottie.xml
new file mode 100644
index 0000000..49fe8c9
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/common/build/templates/lottie.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/empty/__init__.py b/p4a/pythonforandroid/bootstraps/empty/__init__.py
new file mode 100644
index 0000000..8d4c196
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/empty/__init__.py
@@ -0,0 +1,16 @@
+from pythonforandroid.toolchain import Bootstrap
+
+
+class EmptyBootstrap(Bootstrap):
+ name = 'empty'
+
+ recipe_depends = []
+
+ can_be_chosen_automatically = False
+
+ def assemble_distribution(self):
+ print('empty bootstrap has no distribute')
+ exit(1)
+
+
+bootstrap = EmptyBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/empty/build/.gitkeep b/p4a/pythonforandroid/bootstraps/empty/build/.gitkeep
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/empty/build/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/p4a/pythonforandroid/bootstraps/lbry/__init__.py b/p4a/pythonforandroid/bootstraps/lbry/__init__.py
index 0cafc21..e1ae2d8 100644
--- a/p4a/pythonforandroid/bootstraps/lbry/__init__.py
+++ b/p4a/pythonforandroid/bootstraps/lbry/__init__.py
@@ -6,22 +6,21 @@ from os import walk
import glob
import sh
-
EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx")
class LbryBootstrap(Bootstrap):
name = 'lbry'
- recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')]
+ recipe_depends = ['genericndkbuild']
- def run_distribute(self):
+ def assemble_distribution(self):
info_main("# Creating Android project ({})".format(self.name))
arch = self.ctx.archs[0]
- python_install_dir = self.ctx.get_python_install_dir()
- from_crystax = self.ctx.python_recipe.from_crystax
- crystax_python_dir = join("crystax_python", "crystax_python")
+ python_install_dir = self.ctx.get_python_install_dir(arch.arch)
+ #from_crystax = self.ctx.python_recipe.from_crystax
+ #crystax_python_dir = join("crystax_python", "crystax_python")
if len(self.ctx.archs) > 1:
raise ValueError("LBRY/gradle support only one arch")
@@ -39,98 +38,101 @@ class LbryBootstrap(Bootstrap):
with current_directory(self.dist_dir):
info("Copying Python distribution")
- if not exists("private") and not from_crystax:
- ensure_dir("private")
- if not exists("crystax_python") and from_crystax:
- ensure_dir(crystax_python_dir)
+ # if not exists("private"):
+ # ensure_dir("private")
- hostpython = sh.Command(self.ctx.hostpython)
- if not from_crystax:
- try:
- shprint(hostpython, '-OO', '-m', 'compileall',
- python_install_dir,
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(
- sh.cp, '-a', python_install_dir, './python-install')
+ # gethostpython?
+ # hostpython = sh.Command(self.ctx.hostpython)
+ # if not from_crystax:
+ # try:
+ # shprint(hostpython, '-OO', '-m', 'compileall',
+ # python_install_dir,
+ # _tail=10, _filterout="^Listing")
+ # except sh.ErrorReturnCode:
+ # pass
+ # if not exists('python-install'):
+ # shprint(
+ # sh.cp, '-a', python_install_dir, './python-install')
self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
self.distribute_javaclasses(self.ctx.javaclass_dir,
dest_dir=join("src", "main", "java"))
- if not from_crystax:
- info("Filling private directory")
- if not exists(join("private", "lib")):
- info("private/lib does not exist, making")
- shprint(sh.cp, "-a",
- join("python-install", "lib"), "private")
- shprint(sh.mkdir, "-p",
- join("private", "include", "python2.7"))
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
- libpymodules_fn = join("libs", arch.arch, "libpymodules.so")
- if exists(libpymodules_fn):
- shprint(sh.mv, libpymodules_fn, 'private/')
- shprint(sh.cp,
- join('python-install', 'include',
- 'python2.7', 'pyconfig.h'),
- join('private', 'include', 'python2.7/'))
+ # if not from_crystax:
+ # info("Filling private directory")
+ # if not exists(join("private", "lib")):
+ # info("private/lib does not exist, making")
+ # shprint(sh.cp, "-a",
+ # join("python-install", "lib"), "private")
+ # shprint(sh.mkdir, "-p",
+ # join("private", "include", "python2.7"))
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
+ # libpymodules_fn = join("libs", arch.arch, "libpymodules.so")
+ # if exists(libpymodules_fn):
+ # shprint(sh.mv, libpymodules_fn, 'private/')
+ # shprint(sh.cp,
+ # join('python-install', 'include',
+ # 'python2.7', 'pyconfig.h'),
+ # join('private', 'include', 'python2.7/'))
+ #
+ # info('Removing some unwanted files')
+ # shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
+ # shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
- libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
- site_packages_dir = join(libdir, 'site-packages')
- with current_directory(libdir):
- removes = []
- for dirname, root, filenames in walk("."):
- for filename in filenames:
- for suffix in EXCLUDE_EXTS:
- if filename.endswith(suffix):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
+ # libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
+ # with current_directory(libdir):
+ # removes = []
+ # for dirname, root, filenames in walk("."):
+ # for filename in filenames:
+ # for suffix in EXCLUDE_EXTS:
+ # if filename.endswith(suffix):
+ # removes.append(filename)
+ # shprint(sh.rm, '-f', *removes)
+ #
+ # info('Deleting some other stuff not used on android')
+ # # To quote the original distribute.sh, 'well...'
+ # shprint(sh.rm, '-rf', 'lib2to3')
+ # shprint(sh.rm, '-rf', 'idlelib')
+ # for filename in glob.glob('config/libpython*.a'):
+ # shprint(sh.rm, '-f', filename)
+ # shprint(sh.rm, '-rf', 'config/python.o')
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- for filename in glob.glob('config/libpython*.a'):
- shprint(sh.rm, '-f', filename)
- shprint(sh.rm, '-rf', 'config/python.o')
-
- else: # Python *is* loaded from crystax
- ndk_dir = self.ctx.ndk_dir
- py_recipe = self.ctx.python_recipe
- python_dir = join(ndk_dir, 'sources', 'python',
- py_recipe.version, 'libs', arch.arch)
- shprint(sh.cp, '-r', join(python_dir,
- 'stdlib.zip'), crystax_python_dir)
- shprint(sh.cp, '-r', join(python_dir,
- 'modules'), crystax_python_dir)
- shprint(sh.cp, '-r', self.ctx.get_python_install_dir(),
- join(crystax_python_dir, 'site-packages'))
-
- info('Renaming .so files to reflect cross-compile')
- site_packages_dir = join(crystax_python_dir, "site-packages")
- find_ret = shprint(
- sh.find, site_packages_dir, '-iname', '*.so')
- filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1]
- for filename in filenames:
- parts = filename.split('.')
- if len(parts) <= 2:
- continue
- shprint(sh.mv, filename, filename.split('.')[0] + '.so')
- site_packages_dir = join(abspath(curdir),
- site_packages_dir)
+ # else: # Python *is* loaded from crystax
+ # ndk_dir = self.ctx.ndk_dir
+ # py_recipe = self.ctx.python_recipe
+ # python_dir = join(ndk_dir, 'sources', 'python',
+ # py_recipe.version, 'libs', arch.arch)
+ # shprint(sh.cp, '-r', join(python_dir,
+ # 'stdlib.zip'), crystax_python_dir)
+ # shprint(sh.cp, '-r', join(python_dir,
+ # 'modules'), crystax_python_dir)
+ # shprint(sh.cp, '-r', self.ctx.get_python_install_dir(),
+ # join(crystax_python_dir, 'site-packages'))
+ #
+ # info('Renaming .so files to reflect cross-compile')
+ # site_packages_dir = join(crystax_python_dir, "site-packages")
+ # find_ret = shprint(
+ # sh.find, site_packages_dir, '-iname', '*.so')
+ # filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1]
+ # for filename in filenames:
+ # parts = filename.split('.')
+ # if len(parts) <= 2:
+ # continue
+ # shprint(sh.mv, filename, filename.split('.')[0] + '.so')
+ # site_packages_dir = join(abspath(curdir),
+ # site_packages_dir)
if 'sqlite3' not in self.ctx.recipe_build_order:
with open('blacklist.txt', 'a') as fileh:
fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
self.strip_libraries(arch)
self.fry_eggs(site_packages_dir)
- super(LbryBootstrap, self).run_distribute()
+ super().assemble_distribution()
bootstrap = LbryBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py
index 59857a9..49cc5f5 100755
--- a/p4a/pythonforandroid/bootstraps/lbry/build/build.py
+++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py
@@ -435,7 +435,7 @@ main.py that loads it.''')
if exists('build.properties'):
os.remove('build.properties')
-def parse_args(args=None):
+def parse_args_and_make_package(args=None):
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
default_android_api = 12
import argparse
@@ -505,6 +505,10 @@ tools directory of the Android SDK.
default=join(curdir, 'whitelist.txt'),
help=('Use a whitelist file to prevent blacklisting of '
'file in the final APK'))
+ ap.add_argument('--release', dest='build_mode', action='store_const',
+ const='release', default='debug',
+ help='Build your app as a non-debug release build. '
+ '(Disables gdb debugging among other things)')
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 '
@@ -604,4 +608,4 @@ tools directory of the Android SDK.
if __name__ == "__main__":
- parse_args()
+ parse_args_and_make_package()
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties
index 7f81b24..dd012b8 100644
--- a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties
+++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle
index ff67d1d..0522380 100644
--- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle
+++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle
@@ -7,7 +7,7 @@ buildscript {
maven { url "https://jitpack.io" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.4'
+ classpath 'com.android.tools.build:gradle:7.1.2'
}
}
@@ -69,15 +69,15 @@ android {
}
{%- endif %}
- buildTypes {
- debug {
- }
- release {
- {% if args.sign -%}
- signingConfig signingConfigs.release
- {%- endif %}
- }
- }
+// buildTypes {
+// debug {
+// }
+// release {
+// {% if args.sign -%}
+// signingConfig signingConfigs.release
+// {%- endif %}
+// }
+// }
sourceSets {
main {
@@ -104,68 +104,68 @@ subprojects {
}
}
-nexusPublishing {
- repositories {
- sonatype {
- stagingProfileId = sonatypeStagingProfileId
- username = ossrhUsername
- password = ossrhPassword
- nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
- snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
- }
- }
-}
+//nexusPublishing {
+// repositories {
+// sonatype {
+// stagingProfileId = sonatypeStagingProfileId
+// username = ossrhUsername
+// password = ossrhPassword
+// nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+// snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+// }
+// }
+//}
-afterEvaluate {
- publishing {
- publications {
- release(MavenPublication) {
- groupId 'io.lbry'
- artifactId 'lbrysdk64'
- version '{{ args.version }}'
+//afterEvaluate {
+// publishing {
+// publications {
+// release(MavenPublication) {
+// groupId 'io.lbry'
+// artifactId 'lbrysdk64'
+// version '{{ args.version }}'
+//
+// from components.release
+//
+// pom {
+// name = 'LBRY SDK for Android'
+// description = 'The LBRY SDK packaged as an Android AAR'
+// url = 'https://github.com/lbryio/lbry-android-sdk'
+// licenses {
+// license {
+// name = 'MIT License'
+// url = 'https://raw.githubusercontent.com/lbryio/lbry-android-sdk/master/LICENSE'
+// }
+// }
+// developers {
+// developer {
+// id = 'akinwale'
+// name = 'Akinwale Ariwodola'
+// email = 'akinwale@lbry.com'
+// }
+// }
+//
+// scm {
+// url = 'https://github.com/lbryio/lbry-android-sdk'
+// connection = 'scm:git:github.com/lbryio/lbry-android-sdk.git'
+// developerConnection = 'scm:git:ssh://github.com/lbryio/lbry-android-sdk.git'
+// }
+// }
+// }
+// }
+// }
+//}
- from components.release
-
- pom {
- name = 'LBRY SDK for Android'
- description = 'The LBRY SDK packaged as an Android AAR'
- url = 'https://github.com/lbryio/lbry-android-sdk'
- licenses {
- license {
- name = 'MIT License'
- url = 'https://raw.githubusercontent.com/lbryio/lbry-android-sdk/master/LICENSE'
- }
- }
- developers {
- developer {
- id = 'akinwale'
- name = 'Akinwale Ariwodola'
- email = 'akinwale@lbry.com'
- }
- }
-
- scm {
- url = 'https://github.com/lbryio/lbry-android-sdk'
- connection = 'scm:git:github.com/lbryio/lbry-android-sdk.git'
- developerConnection = 'scm:git:ssh://github.com/lbryio/lbry-android-sdk.git'
- }
- }
- }
- }
- }
-}
-
-signing {
- sign publishing.publications
-}
+//signing {
+// sign publishing.publications
+//}
dependencies {
{%- for aar in aars %}
- compile(name: '{{ aar }}', ext: 'aar')
+ implementation (name: '{{ aar }}', ext: 'aar')
{%- endfor -%}
{%- if args.depends -%}
{%- for depend in args.depends %}
- compile '{{ depend }}'
+ implementation '{{ depend }}'
{%- endfor %}
{%- endif %}
}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
index 4f4a6e8..fa54124 100644
--- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
+++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
@@ -166,7 +166,7 @@ dependencies {
{%- endfor -%}
{%- if args.depends -%}
{%- for depend in args.depends %}
- compile '{{ depend }}'
+ implement '{{ depend }}'
{%- endfor %}
{%- endif %}
}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties b/p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties
index 88524aa..7a61dce 100644
--- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties
+++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties
@@ -1,5 +1,6 @@
android.useAndroidX=true
android.enableJetifier=true
+org.gradle.jvmargs=-Xmx4096m
ossrhUsername={{ env["SONATYPE_USERNAME"] }}
ossrhPassword={{ env["SONATYPE_PASSWORD"] }}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/__init__.py b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py
new file mode 100644
index 0000000..662d43c
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py
@@ -0,0 +1,52 @@
+from pythonforandroid.toolchain import (
+ Bootstrap, shprint, current_directory, info, info_main)
+from pythonforandroid.util import ensure_dir
+from os.path import join
+import sh
+
+
+class SDL2GradleBootstrap(Bootstrap):
+ name = 'sdl2'
+
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'sdl2'})
+ )
+
+ def assemble_distribution(self):
+ info_main("# Creating Android project ({})".format(self.name))
+
+ info("Copying SDL2/gradle build")
+ shprint(sh.rm, "-rf", self.dist_dir)
+ shprint(sh.cp, "-r", self.build_dir, self.dist_dir)
+
+ # either the build use environment variable (ANDROID_HOME)
+ # or the local.properties if exists
+ with current_directory(self.dist_dir):
+ with open('local.properties', 'w') as fileh:
+ fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
+
+ with current_directory(self.dist_dir):
+ info("Copying Python distribution")
+
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
+
+ for arch in self.ctx.archs:
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+
+ if 'sqlite3' not in self.ctx.recipe_build_order:
+ with open('blacklist.txt', 'a') as fileh:
+ fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
+
+ super().assemble_distribution()
+
+
+bootstrap = SDL2GradleBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore
new file mode 100644
index 0000000..a1fc39c
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore
@@ -0,0 +1,14 @@
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt b/p4a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt
new file mode 100644
index 0000000..d5e230c
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt
@@ -0,0 +1,84 @@
+# prevent user to include invalid extensions
+*.apk
+*.aab
+*.apks
+*.pxd
+
+# eggs
+*.egg-info
+
+# unit test
+unittest/*
+
+# python config
+config/makesetup
+
+# unused kivy files (platform specific)
+kivy/input/providers/wm_*
+kivy/input/providers/mactouch*
+kivy/input/providers/probesysfs*
+kivy/input/providers/mtdev*
+kivy/input/providers/hidinput*
+kivy/core/camera/camera_videocapture*
+kivy/core/spelling/*osx*
+kivy/core/video/video_pyglet*
+kivy/tools
+kivy/tests/*
+kivy/*/*.h
+kivy/*/*.pxi
+
+# unused encodings
+lib-dynload/*codec*
+encodings/cp*.pyo
+encodings/tis*
+encodings/shift*
+encodings/bz2*
+encodings/iso*
+encodings/undefined*
+encodings/johab*
+encodings/p*
+encodings/m*
+encodings/euc*
+encodings/k*
+encodings/unicode_internal*
+encodings/quo*
+encodings/gb*
+encodings/big5*
+encodings/hp*
+encodings/hz*
+
+# unused python modules
+bsddb/*
+wsgiref/*
+hotshot/*
+pydoc_data/*
+tty.pyo
+anydbm.pyo
+nturl2path.pyo
+LICENCE.txt
+macurl2path.pyo
+dummy_threading.pyo
+audiodev.pyo
+antigravity.pyo
+dumbdbm.pyo
+sndhdr.pyo
+__phello__.foo.pyo
+sunaudio.pyo
+os2emxpath.pyo
+multiprocessing/dummy*
+
+# unused binaries python modules
+lib-dynload/termios.so
+lib-dynload/_lsprof.so
+lib-dynload/*audioop.so
+lib-dynload/_hotshot.so
+lib-dynload/_heapq.so
+lib-dynload/_json.so
+lib-dynload/grp.so
+lib-dynload/resource.so
+lib-dynload/pyexpat.so
+lib-dynload/_ctypes_test.so
+lib-dynload/_testcapi.so
+
+# odd files
+plat-linux3/regen
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk
new file mode 100644
index 0000000..1559853
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk
@@ -0,0 +1,8 @@
+
+# Uncomment this if you're using STL in your project
+# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
+# APP_STL := stlport_static
+
+# APP_ABI := armeabi armeabi-v7a x86
+APP_ABI := $(ARCH)
+APP_PLATFORM := $(NDK_API)
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk
new file mode 100644
index 0000000..517660b
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main
+
+LOCAL_SRC_FILES := start.c
+
+LOCAL_STATIC_LIBRARIES := SDL2_static
+
+include $(BUILD_SHARED_LIBRARY)
+$(call import-module,SDL)LOCAL_PATH := $(call my-dir)
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 0000000..83dec51
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,5 @@
+
+#define BOOTSTRAP_NAME_SDL2
+
+const char bootstrap_name[] = "SDL2"; // capitalized for historic reasons
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
new file mode 100644
index 0000000..58a1c5e
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
@@ -0,0 +1,19 @@
+package org.kivy.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.Context;
+
+public class GenericBroadcastReceiver extends BroadcastReceiver {
+
+ GenericBroadcastReceiverCallback listener;
+
+ public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {
+ super();
+ this.listener = listener;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ this.listener.onReceive(context, intent);
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
new file mode 100644
index 0000000..1a87c98
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
@@ -0,0 +1,8 @@
+package org.kivy.android;
+
+import android.content.Intent;
+import android.content.Context;
+
+public interface GenericBroadcastReceiverCallback {
+ void onReceive(Context context, Intent intent);
+};
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 0000000..361975a
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,643 @@
+package org.kivy.android;
+
+import java.io.InputStream;
+import java.io.FileWriter;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+import android.content.res.Resources.NotFoundException;
+
+import org.libsdl.app.SDLActivity;
+
+import org.kivy.android.launcher.Project;
+
+import org.renpy.android.ResourceManager;
+
+
+public class PythonActivity extends SDLActivity {
+ private static final String TAG = "PythonActivity";
+
+ public static PythonActivity mActivity = null;
+
+ private ResourceManager resourceManager = null;
+ private Bundle mMetaData = null;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "PythonActivity onCreate running");
+ resourceManager = new ResourceManager(this);
+
+ Log.v(TAG, "About to do super onCreate");
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "Did super onCreate");
+
+ this.mActivity = this;
+ this.showLoadingScreen(this.getLoadingScreen());
+
+ new UnpackFilesTask().execute(getAppRoot());
+ }
+
+ public void loadLibraries() {
+ String app_root = new String(getAppRoot());
+ File app_root_file = new File(app_root);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
+ }
+
+ /**
+ * Show an error using a toast. (Only makes sense from non-UI
+ * threads.)
+ */
+ public void toastError(final String msg) {
+
+ final Activity thisActivity = this;
+
+ runOnUiThread(new Runnable () {
+ public void run() {
+ Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();
+ }
+ });
+
+ // Wait to show the error.
+ synchronized (this) {
+ try {
+ this.wait(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ private class UnpackFilesTask extends AsyncTask {
+ @Override
+ protected String doInBackground(String... params) {
+ File app_root_file = new File(params[0]);
+ Log.v(TAG, "Ready to unpack");
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ // Figure out the directory where the game is. If the game was
+ // given to us via an intent, then we use the scheme-specific
+ // part of that intent to determine the file to launch. We
+ // also use the android.txt file to determine the orientation.
+ //
+ // Otherwise, we use the public data, if we have it, or the
+ // private data if we do not.
+ mActivity.finishLoad();
+
+ // finishLoad called setContentView with the SDL view, which
+ // removed the loading screen. However, we still need it to
+ // show until the app is ready to render, so pop it back up
+ // on top of the SDL view.
+ mActivity.showLoadingScreen(getLoadingScreen());
+
+ String app_root_dir = getAppRoot();
+ if (getIntent() != null && getIntent().getAction() != null &&
+ getIntent().getAction().equals("org.kivy.LAUNCH")) {
+ File path = new File(getIntent().getData().getSchemeSpecificPart());
+
+ Project p = Project.scanDirectory(path);
+ String entry_point = getEntryPoint(p.dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", p.dir + "/" + entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", p.dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", p.dir);
+
+ if (p != null) {
+ if (p.landscape) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ }
+
+ // Let old apps know they started.
+ try {
+ FileWriter f = new FileWriter(new File(path, ".launch"));
+ f.write("started");
+ f.close();
+ } catch (IOException e) {
+ // pass
+ }
+ } else {
+ String entry_point = getEntryPoint(app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ }
+
+ String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ Log.v(TAG, "Setting env vars for start.c and Python to use");
+ SDLActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ SDLActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ SDLActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
+
+ try {
+ Log.v(TAG, "Access to our meta-data...");
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
+ }
+ if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) {
+ Log.v(TAG, "Surface will be transparent.");
+ getSurface().setZOrderOnTop(true);
+ getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT);
+ } else {
+ Log.i(TAG, "Surface will NOT be transparent");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ // Launch app if that hasn't been done yet:
+ if (mActivity.mHasFocus && (
+ // never went into proper resume state:
+ mActivity.mCurrentNativeState == NativeState.INIT ||
+ (
+ // resumed earlier but wasn't ready yet
+ mActivity.mCurrentNativeState == NativeState.RESUMED &&
+ mActivity.mSDLThread == null
+ ))) {
+ // Because sometimes the app will get stuck here and never
+ // actually run, ensure that it gets launched if we're active:
+ mActivity.resumeNativeThread();
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... values) {
+ }
+ }
+
+ public static ViewGroup getLayout() {
+ return mLayout;
+ }
+
+ public static SurfaceView getSurface() {
+ return mSurface;
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onNewIntent
+ //
+
+ public interface NewIntentListener {
+ void onNewIntent(Intent intent);
+ }
+
+ private List newIntentListeners = null;
+
+ public void registerNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ this.newIntentListeners = Collections.synchronizedList(new ArrayList());
+ this.newIntentListeners.add(listener);
+ }
+
+ public void unregisterNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.newIntentListeners.remove(listener);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.newIntentListeners ) {
+ Iterator iterator = this.newIntentListeners.iterator();
+ while ( iterator.hasNext() ) {
+ (iterator.next()).onNewIntent(intent);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onActivityResult
+ //
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ private List activityResultListeners = null;
+
+ public void registerActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ this.activityResultListeners = Collections.synchronizedList(new ArrayList());
+ this.activityResultListeners.add(listener);
+ }
+
+ public void unregisterActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.activityResultListeners.remove(listener);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.activityResultListeners ) {
+ Iterator iterator = this.activityResultListeners.iterator();
+ while ( iterator.hasNext() )
+ (iterator.next()).onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
+ serviceIntent.putExtra("androidPrivate", argument);
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
+ serviceIntent.putExtra("serviceTitle", serviceTitle);
+ serviceIntent.putExtra("serviceDescription", serviceDescription);
+ serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ PythonActivity.mActivity.startService(serviceIntent);
+ }
+
+ public static void stop_service() {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ PythonActivity.mActivity.stopService(serviceIntent);
+ }
+
+ /** Loading screen view **/
+ public static ImageView mImageView = null;
+ public static View mLottieView = null;
+ /** Whether main routine/actual app has started yet **/
+ protected boolean mAppConfirmedActive = false;
+ /** Timer for delayed loading screen removal. **/
+ protected Timer loadingScreenRemovalTimer = null;
+
+ // Overridden since it's called often, to check whether to remove the
+ // loading screen:
+ @Override
+ protected boolean sendCommand(int command, Object data) {
+ boolean result = super.sendCommand(command, data);
+ considerLoadingScreenRemoval();
+ return result;
+ }
+
+ /** Confirm that the app's main routine has been launched.
+ **/
+ @Override
+ public void appConfirmedActive() {
+ if (!mAppConfirmedActive) {
+ Log.v(TAG, "appConfirmedActive() -> preparing loading screen removal");
+ mAppConfirmedActive = true;
+ considerLoadingScreenRemoval();
+ }
+ }
+
+ /** This is called from various places to check whether the app's main
+ * routine has been launched already, and if it has, then the loading
+ * screen will be removed.
+ **/
+ public void considerLoadingScreenRemoval() {
+ if (loadingScreenRemovalTimer != null)
+ return;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (((PythonActivity)PythonActivity.mSingleton).mAppConfirmedActive &&
+ loadingScreenRemovalTimer == null) {
+ // Remove loading screen but with a delay.
+ // (app can use p4a's android.loadingscreen module to
+ // do it quicker if it wants to)
+ // get a handler (call from main thread)
+ // this will run when timer elapses
+ TimerTask removalTask = new TimerTask() {
+ @Override
+ public void run() {
+ // post a runnable to the handler
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ PythonActivity activity =
+ ((PythonActivity)PythonActivity.mSingleton);
+ if (activity != null)
+ activity.removeLoadingScreen();
+ }
+ });
+ }
+ };
+ loadingScreenRemovalTimer = new Timer();
+ loadingScreenRemovalTimer.schedule(removalTask, 5000);
+ }
+ }
+ });
+ }
+
+ public void removeLoadingScreen() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ View view = mLottieView != null ? mLottieView : mImageView;
+ if (view != null && view.getParent() != null) {
+ ((ViewGroup)view.getParent()).removeView(view);
+ mLottieView = null;
+ mImageView = null;
+ }
+ }
+ });
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
+ protected void showLoadingScreen(View view) {
+ try {
+ if (mLayout == null) {
+ setContentView(view);
+ } else if (view.getParent() == null) {
+ mLayout.addView(view);
+ }
+ } catch (IllegalStateException e) {
+ // The loading screen can be attempted to be applied twice if app
+ // is tabbed in/out, quickly.
+ // (Gives error "The specified child already has a parent.
+ // You must call removeView() on the child's parent first.")
+ }
+ }
+
+ protected void setBackgroundColor(View view) {
+ /*
+ * Set the presplash loading screen background color
+ * https://developer.android.com/reference/android/graphics/Color.html
+ * Parse the color string, and return the corresponding color-int.
+ * If the string cannot be parsed, throws an IllegalArgumentException exception.
+ * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+ * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
+ * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
+ * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
+ */
+ String backgroundColor = resourceManager.getString("presplash_color");
+ if (backgroundColor != null) {
+ try {
+ view.setBackgroundColor(Color.parseColor(backgroundColor));
+ } catch (IllegalArgumentException e) {}
+ }
+ }
+
+ protected View getLoadingScreen() {
+ // If we have an mLottieView or mImageView already, then do
+ // nothing because it will have already been made the content
+ // view or added to the layout.
+ if (mLottieView != null || mImageView != null) {
+ // we already have a splash screen
+ return mLottieView != null ? mLottieView : mImageView;
+ }
+
+ // first try to load the lottie one
+ try {
+ mLottieView = getLayoutInflater().inflate(
+ this.resourceManager.getIdentifier("lottie", "layout"),
+ mLayout,
+ false
+ );
+ try {
+ if (mLayout == null) {
+ setContentView(mLottieView);
+ } else if (PythonActivity.mLottieView.getParent() == null) {
+ mLayout.addView(mLottieView);
+ }
+ } catch (IllegalStateException e) {
+ // The loading screen can be attempted to be applied twice if app
+ // is tabbed in/out, quickly.
+ // (Gives error "The specified child already has a parent.
+ // You must call removeView() on the child's parent first.")
+ }
+ setBackgroundColor(mLottieView);
+ return mLottieView;
+ }
+ catch (NotFoundException e) {
+ Log.v("SDL", "couldn't find lottie layout or animation, trying static splash");
+ }
+
+ // no lottie asset, try to load the static image then
+ int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
+ InputStream is = this.getResources().openRawResource(presplashId);
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {};
+ }
+
+ mImageView = new ImageView(this);
+ mImageView.setImageBitmap(bitmap);
+ setBackgroundColor(mImageView);
+
+ mImageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ return mImageView;
+ }
+
+ @Override
+ protected void onPause() {
+ if (this.mWakeLock != null && mWakeLock.isHeld()) {
+ this.mWakeLock.release();
+ }
+
+ Log.v(TAG, "onPause()");
+ try {
+ super.onPause();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch pause while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ if (this.mWakeLock != null) {
+ this.mWakeLock.acquire();
+ }
+ Log.v(TAG, "onResume()");
+ try {
+ super.onResume();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch resume while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ try {
+ super.onWindowFocusChanged(hasFocus);
+ } catch (UnsatisfiedLinkError e) {
+ // Catch window focus while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
+ }
+
+ /**
+ * Used by android.permissions p4a module to register a call back after
+ * requesting runtime permissions
+ **/
+ public interface PermissionsCallback {
+ void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
+ }
+
+ private PermissionsCallback permissionCallback;
+ private boolean havePermissionsCallback = false;
+
+ public void addPermissionsCallback(PermissionsCallback callback) {
+ permissionCallback = callback;
+ havePermissionsCallback = true;
+ Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult");
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ Log.v(TAG, "onRequestPermissionsResult()");
+ if (havePermissionsCallback) {
+ Log.v(TAG, "onRequestPermissionsResult passed to callback");
+ permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /**
+ * Used by android.permissions p4a module to check a permission
+ **/
+ public boolean checkCurrentPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return true;
+
+ try {
+ java.lang.reflect.Method methodCheckPermission =
+ Activity.class.getMethod("checkSelfPermission", String.class);
+ Object resultObj = methodCheckPermission.invoke(this, permission);
+ int result = Integer.parseInt(resultObj.toString());
+ if (result == PackageManager.PERMISSION_GRANTED)
+ return true;
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Used by android.permissions p4a module to request runtime permissions
+ **/
+ public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return;
+ try {
+ java.lang.reflect.Method methodRequestPermission =
+ Activity.class.getMethod("requestPermissions",
+ String[].class, int.class);
+ methodRequestPermission.invoke(this, permissions, requestCode);
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ }
+
+ public void requestPermissions(String[] permissions) {
+ requestPermissionsWithRequestCode(permissions, 1);
+ }
+
+ public static void changeKeyboard(int inputType) {
+ if (SDLActivity.keyboardInputType != inputType){
+ SDLActivity.keyboardInputType = inputType;
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.restartInput(mTextEdit);
+ }
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java
new file mode 100644
index 0000000..9177b43
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java
@@ -0,0 +1,99 @@
+package org.kivy.android.launcher;
+
+import java.io.UnsupportedEncodingException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import android.util.Log;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+
+/**
+ * This represents a project we've scanned for.
+ */
+public class Project {
+
+ public String dir = null;
+ String title = null;
+ String author = null;
+ Bitmap icon = null;
+ public boolean landscape = false;
+
+ static String decode(String s) {
+ try {
+ return new String(s.getBytes("ISO-8859-1"), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return s;
+ }
+ }
+
+ /**
+ * Scans directory for a android.txt file. If it finds one,
+ * and it looks valid enough, then it creates a new Project,
+ * and returns that. Otherwise, returns null.
+ */
+ public static Project scanDirectory(File dir) {
+
+ // We might have a link file.
+ if (dir.getAbsolutePath().endsWith(".link")) {
+ try {
+
+ // Scan the android.txt file.
+ File propfile = new File(dir, "android.txt");
+ FileInputStream in = new FileInputStream(propfile);
+ Properties p = new Properties();
+ p.load(in);
+ in.close();
+
+ String directory = p.getProperty("directory", null);
+
+ if (directory == null) {
+ return null;
+ }
+
+ dir = new File(directory);
+
+ } catch (Exception e) {
+ Log.i("Project", "Couldn't open link file " + dir, e);
+ }
+ }
+
+ // Make sure we're dealing with a directory.
+ if (! dir.isDirectory()) {
+ return null;
+ }
+
+ try {
+
+ // Scan the android.txt file.
+ File propfile = new File(dir, "android.txt");
+ FileInputStream in = new FileInputStream(propfile);
+ Properties p = new Properties();
+ p.load(in);
+ in.close();
+
+ // Get the various properties.
+ String title = decode(p.getProperty("title", "Untitled"));
+ String author = decode(p.getProperty("author", ""));
+ boolean landscape = p.getProperty("orientation", "portrait").equals("landscape");
+
+ // Create the project object.
+ Project rv = new Project();
+ rv.title = title;
+ rv.author = author;
+ rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath());
+ rv.landscape = landscape;
+ rv.dir = dir.getAbsolutePath();
+
+ return rv;
+
+ } catch (Exception e) {
+ Log.i("Project", "Couldn't open android.txt", e);
+ }
+
+ return null;
+
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
new file mode 100644
index 0000000..457f83f
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
@@ -0,0 +1,35 @@
+package org.kivy.android.launcher;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.ImageView;
+
+import org.renpy.android.ResourceManager;
+
+public class ProjectAdapter extends ArrayAdapter {
+
+ private ResourceManager resourceManager;
+
+ public ProjectAdapter(Activity context) {
+ super(context, 0);
+ resourceManager = new ResourceManager(context);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Project p = getItem(position);
+
+ View v = resourceManager.inflateView("chooser_item");
+ TextView title = (TextView) resourceManager.getViewById(v, "title");
+ TextView author = (TextView) resourceManager.getViewById(v, "author");
+ ImageView icon = (ImageView) resourceManager.getViewById(v, "icon");
+
+ title.setText(p.title);
+ author.setText(p.author);
+ icon.setImageBitmap(p.icon);
+
+ return v;
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
new file mode 100644
index 0000000..486f88b
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
@@ -0,0 +1,90 @@
+package org.kivy.android.launcher;
+
+import android.app.Activity;
+
+import android.content.Intent;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.AdapterView;
+import android.os.Environment;
+
+import java.io.File;
+import java.util.Arrays;
+import android.net.Uri;
+
+import org.renpy.android.ResourceManager;
+
+public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener {
+
+ ResourceManager resourceManager;
+
+ String urlScheme;
+
+ @Override
+ public void onStart()
+ {
+ super.onStart();
+
+ resourceManager = new ResourceManager(this);
+
+ urlScheme = resourceManager.getString("urlScheme");
+
+ // Set the window title.
+ setTitle(resourceManager.getString("appName"));
+
+ // Scan the sdcard for files, and sort them.
+ File dir = new File(Environment.getExternalStorageDirectory(), urlScheme);
+
+ File entries[] = dir.listFiles();
+
+ if (entries == null) {
+ entries = new File[0];
+ }
+
+ Arrays.sort(entries);
+
+ // Create a ProjectAdapter and fill it with projects.
+ ProjectAdapter projectAdapter = new ProjectAdapter(this);
+
+ // Populate it with the properties files.
+ for (File d : entries) {
+ Project p = Project.scanDirectory(d);
+ if (p != null) {
+ projectAdapter.add(p);
+ }
+ }
+
+ if (projectAdapter.getCount() != 0) {
+
+ View v = resourceManager.inflateView("project_chooser");
+ ListView l = (ListView) resourceManager.getViewById(v, "projectList");
+
+ l.setAdapter(projectAdapter);
+ l.setOnItemClickListener(this);
+
+ setContentView(v);
+
+ } else {
+
+ View v = resourceManager.inflateView("project_empty");
+ TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText");
+
+ emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit.");
+
+ setContentView(v);
+ }
+ }
+
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ Project p = (Project) parent.getItemAtPosition(position);
+
+ Intent intent = new Intent(
+ "org.kivy.LAUNCH",
+ Uri.fromParts(urlScheme, p.dir, ""));
+
+ intent.setClassName(getPackageName(), "org.kivy.android.PythonActivity");
+ this.startActivity(intent);
+ this.finish();
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/libs/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/libs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..d50bdaa
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0a299eb
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a336ad5
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d423dac
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml
new file mode 100644
index 0000000..1823b13
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml
new file mode 100644
index 0000000..123c4b6
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml
new file mode 100644
index 0000000..23828e6
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml
new file mode 100644
index 0000000..ee54814
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch b/p4a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
new file mode 100644
index 0000000..d061be8
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
@@ -0,0 +1,75 @@
+--- a/src/main/java/org/libsdl/app/SDLActivity.java
++++ b/src/main/java/org/libsdl/app/SDLActivity.java
+@@ -222,6 +222,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ protected static Thread mSDLThread;
+
++ public static int keyboardInputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
++
+ protected static SDLGenericMotionListener_API12 getMotionListener() {
+ if (mMotionListener == null) {
+ if (Build.VERSION.SDK_INT >= 26) {
+@@ -324,6 +326,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+
++ SDLActivity.initialize();
++ // So we can call stuff from static callbacks
++ mSingleton = this;
++ }
++
++ // We don't do this in onCreate because we unpack and load the app data on a thread
++ // and we can't run setup tasks until that thread completes.
++ protected void finishLoad() {
++
+ try {
+ Thread.currentThread().setName("SDLActivity");
+ } catch (Exception e) {
+@@ -835,7 +846,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ Handler commandHandler = new SDLCommandHandler();
+
+ // Send a message from the SDLMain thread
+- boolean sendCommand(int command, Object data) {
++ protected boolean sendCommand(int command, Object data) {
+ Message msg = commandHandler.obtainMessage();
+ msg.arg1 = command;
+ msg.obj = data;
+@@ -1384,6 +1395,20 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ return SDLActivity.mSurface.getNativeSurface();
+ }
+
++ /**
++ * Calls turnActive() on singleton to keep loading screen active
++ */
++ public static void triggerAppConfirmedActive() {
++ mSingleton.appConfirmedActive();
++ }
++
++ /**
++ * Trick needed for loading screen, overridden by PythonActivity
++ * to keep loading screen active
++ */
++ public void appConfirmedActive() {
++ }
++
+ // Input
+
+ /**
+@@ -1878,6 +1903,7 @@ class SDLMain implements Runnable {
+
+ Log.v("SDL", "Running main function " + function + " from library " + library);
+
++ SDLActivity.mSingleton.appConfirmedActive();
+ SDLActivity.nativeRunMain(library, function, arguments);
+
+ Log.v("SDL", "Finished main function");
+@@ -1935,8 +1961,7 @@ class DummyEdit extends View implements View.OnKeyListener {
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ ic = new SDLInputConnection(this, true);
+
+- outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
+- InputType.TYPE_TEXT_FLAG_MULTI_LINE;
++ outAttrs.inputType = SDLActivity.keyboardInputType | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
+ EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 0000000..b5ddde3
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,146 @@
+
+
+
+
+ = 9 %}
+ android:xlargeScreens="true"
+ {% endif %}
+ />
+
+
+
+
+
+
+
+
+
+ {% for perm in args.permissions %}
+ {% if '.' in perm %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+ {% if args.wakelock %}
+
+ {% endif %}
+
+ {% if args.billing_pubkey %}
+
+ {% endif %}
+
+ {{ args.extra_manifest_xml }}
+
+
+
+
+ {% for l in args.android_used_libs %}
+
+ {% endfor %}
+
+ {% for m in args.meta_data %}
+ {% endfor %}
+
+
+
+
+ {% if args.launcher %}
+
+
+
+
+
+ {% else %}
+
+
+
+
+ {% endif %}
+
+ {%- if args.intent_filters -%}
+ {{- args.intent_filters -}}
+ {%- endif -%}
+
+
+ {% if args.launcher %}
+
+
+
+
+
+
+
+
+ {% endif %}
+
+ {% if service or args.launcher %}
+
+ {% endif %}
+ {% for name in service_names %}
+
+ {% endfor %}
+ {% for name in native_services %}
+
+ {% endfor %}
+
+ {% if args.billing_pubkey %}
+
+
+
+
+
+
+
+
+ {% endif %}
+ {% for a in args.add_activity %}
+
+ {% endfor %}
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/templates/strings.tmpl.xml b/p4a/pythonforandroid/bootstraps/sdl2/build/templates/strings.tmpl.xml
new file mode 100644
index 0000000..c802551
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/sdl2/build/templates/strings.tmpl.xml
@@ -0,0 +1,7 @@
+
+
+ {{ args.name }}
+ {{ private_version }}
+ {{ args.presplash_color }}
+ {{ url_scheme }}
+
diff --git a/p4a/pythonforandroid/bootstraps/service_library/__init__.py b/p4a/pythonforandroid/bootstraps/service_library/__init__.py
new file mode 100644
index 0000000..0b41be8
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/__init__.py
@@ -0,0 +1,9 @@
+from pythonforandroid.bootstraps.service_only import ServiceOnlyBootstrap
+
+
+class ServiceLibraryBootstrap(ServiceOnlyBootstrap):
+
+ name = 'service_library'
+
+
+bootstrap = ServiceLibraryBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h b/p4a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 0000000..01fd122
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,6 @@
+
+#define BOOTSTRAP_NAME_LIBRARY
+#define BOOTSTRAP_USES_NO_SDL_HEADERS
+
+const char bootstrap_name[] = "service_library";
+
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
new file mode 100644
index 0000000..58a1c5e
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
@@ -0,0 +1,19 @@
+package org.kivy.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.Context;
+
+public class GenericBroadcastReceiver extends BroadcastReceiver {
+
+ GenericBroadcastReceiverCallback listener;
+
+ public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {
+ super();
+ this.listener = listener;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ this.listener.onReceive(context, intent);
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
new file mode 100644
index 0000000..1a87c98
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
@@ -0,0 +1,8 @@
+package org.kivy.android;
+
+import android.content.Intent;
+import android.content.Context;
+
+public interface GenericBroadcastReceiverCallback {
+ void onReceive(Context context, Intent intent);
+};
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 0000000..7be751d
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,9 @@
+package org.kivy.android;
+
+import android.app.Activity;
+
+// Required by PythonService class
+public class PythonActivity extends Activity {
+ public static PythonActivity mActivity = null;
+}
+
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/src/main/res/mipmap/.gitkeep b/p4a/pythonforandroid/bootstraps/service_library/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 0000000..f667651
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ {% for name in service_names %}
+
+ {% endfor %}
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java
new file mode 100644
index 0000000..f1eaf07
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java
@@ -0,0 +1,82 @@
+package {{ args.package }};
+
+import java.io.File;
+
+import android.os.Build;
+import android.content.Intent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+import org.kivy.android.PythonService;
+import org.kivy.android.PythonUtil;
+
+public class Service{{ name|capitalize }} extends PythonService {
+
+ private static final String TAG = "PythonService";
+
+ {% if sticky %}
+ @Override
+ public int startType() {
+ return START_STICKY;
+ }
+ {% endif %}
+
+ @Override
+ protected int getServiceId() {
+ return {{ service_id }};
+ }
+
+ public static void prepare(Context ctx) {
+ String appRoot = PythonUtil.getAppRoot(ctx);
+ Log.v(TAG, "Ready to unpack");
+ File app_root_file = new File(appRoot);
+ PythonUtil.unpackAsset(ctx, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(ctx, ctx.getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+ }
+
+ public static void start(Context ctx, String pythonServiceArgument) {
+ Intent intent = getDefaultIntent(ctx, pythonServiceArgument);
+
+ //foreground: {{foreground}}
+ {% if foreground %}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ ctx.startForegroundService(intent);
+ } else {
+ ctx.startService(intent);
+ }
+ {% else %}
+ ctx.startService(intent);
+ {% endif %}
+ }
+
+ static public Intent getDefaultIntent(Context ctx, String pythonServiceArgument) {
+ String appRoot = PythonUtil.getAppRoot(ctx);
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ intent.putExtra("androidPrivate", appRoot);
+ intent.putExtra("androidArgument", appRoot);
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("serviceTitle", "{{ name|capitalize }}");
+ intent.putExtra("serviceDescription", "");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
+ intent.putExtra("pythonHome", appRoot);
+ intent.putExtra("androidUnpack", appRoot);
+ intent.putExtra("pythonPath", appRoot + ":" + appRoot + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ return intent;
+ }
+
+ @Override
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return Service{{ name|capitalize }}.getDefaultIntent(ctx, pythonServiceArgument);
+ }
+
+
+
+ static public void stop(Context ctx) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ ctx.stopService(intent);
+ }
+
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/activity_service_control.xml b/p4a/pythonforandroid/bootstraps/service_library/build/templates/activity_service_control.xml
new file mode 100644
index 0000000..8ab7f21
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/activity_service_control.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/build.tmpl.gradleNO b/p4a/pythonforandroid/bootstraps/service_library/build/templates/build.tmpl.gradleNO
new file mode 100644
index 0000000..0fb4216
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/build.tmpl.gradleNO
@@ -0,0 +1,172 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ mavenCentral()
+ maven { url "https://jitpack.io" }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.1.2'
+ }
+}
+
+plugins {
+ id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {
+ url 'https://maven.google.com'
+ }
+ flatDir {
+ dirs 'libs'
+ }
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+group = "io.lbry"
+version = "{{ args.version }}"
+
+android {
+ compileSdkVersion {{ android_api }}
+ buildToolsVersion '{{ build_tools_version }}'
+ defaultConfig {
+ minSdkVersion {{ args.min_sdk_version }}
+ targetSdkVersion {{ android_api }}
+ versionCode {{ args.numeric_version }} * 10 + 2
+ versionName '{{ args.version }}'
+ multiDexEnabled true
+
+ ndk {
+ abiFilters "arm64-v8a"
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+
+ dexOptions {
+ jumboMode true
+ }
+
+ {% 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 %}
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDir 'libs'
+ }
+ }
+}
+
+ext {
+ compileSdkVersion = {{ android_api }}
+ buildToolsVersion = '{{ build_tools_version }}'
+ minSdkVersion = {{ args.min_sdk_version }}
+ targetSdkVersion = {{ android_api }}
+}
+
+subprojects {
+ afterEvaluate {project ->
+ if (project.hasProperty("android")) {
+ android {
+ compileSdkVersion {{ android_api }}
+ buildToolsVersion '{{ build_tools_version }}'
+ }
+ }
+ }
+}
+
+//nexusPublishing {
+// repositories {
+// sonatype {
+// stagingProfileId = sonatypeStagingProfileId
+// username = ossrhUsername
+// password = ossrhPassword
+// nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+// snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+// }
+// }
+//}
+
+afterEvaluate {
+ println("Components: " + components*.name)
+ publishing {
+ publications {
+ release(MavenPublication) {
+ groupId 'io.lbry'
+ artifactId 'lbrysdk64'
+ version '{{ args.version }}'
+
+ from components.release
+
+ pom {
+ name = 'LBRY SDK for Android'
+ description = 'The LBRY SDK packaged as an Android AAR'
+ url = 'https://github.com/lbryio/lbry-android-sdk'
+ licenses {
+ license {
+ name = 'MIT License'
+ url = 'https://raw.githubusercontent.com/lbryio/lbry-android-sdk/master/LICENSE'
+ }
+ }
+ developers {
+ developer {
+ id = 'akinwale'
+ name = 'Akinwale Ariwodola'
+ email = 'akinwale@lbry.com'
+ }
+ }
+
+ scm {
+ url = 'https://github.com/lbryio/lbry-android-sdk'
+ connection = 'scm:git:github.com/lbryio/lbry-android-sdk.git'
+ developerConnection = 'scm:git:ssh://github.com/lbryio/lbry-android-sdk.git'
+ }
+ }
+ }
+ }
+ }
+}
+
+signing {
+ sign publishing.publications
+}
+
+dependencies {
+ {%- for aar in aars %}
+ compile(name: '{{ aar }}', ext: 'aar')
+ {%- endfor -%}
+ {%- if args.depends -%}
+ {%- for depend in args.depends %}
+ implementation '{{ depend }}'
+ {%- endfor %}
+ {%- endif %}
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/colors.tmpl.xml b/p4a/pythonforandroid/bootstraps/service_library/build/templates/colors.tmpl.xml
new file mode 100644
index 0000000..1c703f9
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/colors.tmpl.xml
@@ -0,0 +1,12 @@
+
+
+ #40B89A
+ #303F9F
+ #FFFFFF
+
+ #FF0000
+ #00C000
+ #FFFFFF
+ #2F9176
+ #38D9A9
+
\ No newline at end of file
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/gradle.properties b/p4a/pythonforandroid/bootstraps/service_library/build/templates/gradle.properties
new file mode 100644
index 0000000..88524aa
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_library/build/templates/gradle.properties
@@ -0,0 +1,10 @@
+android.useAndroidX=true
+android.enableJetifier=true
+
+ossrhUsername={{ env["SONATYPE_USERNAME"] }}
+ossrhPassword={{ env["SONATYPE_PASSWORD"] }}
+sonatypeStagingProfileId={{ env["SONATYPE_STAGING_PROFILE_ID"] }}
+
+signing.keyId={{ env["NEXUS_SIGNING_KEY_ID"] }}
+signing.password={{ env["NEXUS_SIGNING_KEY_PASSWORD"] }}
+signing.secretKeyRingFile={{ env["NEXUS_SIGNING_KEYRING_FILE"] }}
diff --git a/p4a/pythonforandroid/bootstraps/service_library/build/templates/lbry-icon.png b/p4a/pythonforandroid/bootstraps/service_library/build/templates/lbry-icon.png
new file mode 100644
index 0000000..fb78a16
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/service_library/build/templates/lbry-icon.png differ
diff --git a/p4a/pythonforandroid/bootstraps/service_only/__init__.py b/p4a/pythonforandroid/bootstraps/service_only/__init__.py
new file mode 100644
index 0000000..b9e000c
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/__init__.py
@@ -0,0 +1,52 @@
+import sh
+from os.path import join
+from pythonforandroid.toolchain import (
+ Bootstrap, current_directory, info, info_main, shprint)
+from pythonforandroid.util import ensure_dir
+
+
+class ServiceOnlyBootstrap(Bootstrap):
+
+ name = 'service_only'
+
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
+
+ def assemble_distribution(self):
+ info_main('# Creating Android project from build and {} bootstrap'.format(
+ self.name))
+
+ info('This currently just copies the build stuff straight from the build dir.')
+ shprint(sh.rm, '-rf', self.dist_dir)
+ shprint(sh.cp, '-r', self.build_dir, self.dist_dir)
+ with current_directory(self.dist_dir):
+ with open('local.properties', 'w') as fileh:
+ fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
+
+ with current_directory(self.dist_dir):
+ info('Copying python distribution')
+
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
+
+ for arch in self.ctx.archs:
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ self.distribute_aars(arch)
+
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+
+ if 'sqlite3' not in self.ctx.recipe_build_order:
+ with open('blacklist.txt', 'a') as fileh:
+ fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
+
+ super().assemble_distribution()
+
+
+bootstrap = ServiceOnlyBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/blacklist.txt b/p4a/pythonforandroid/bootstraps/service_only/build/blacklist.txt
new file mode 100644
index 0000000..53cc634
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/build/blacklist.txt
@@ -0,0 +1,91 @@
+# prevent user to include invalid extensions
+*.apk
+*.aab
+*.apks
+*.pxd
+
+# eggs
+*.egg-info
+
+# unit test
+unittest/*
+
+# python config
+config/makesetup
+
+# unused kivy files (platform specific)
+kivy/input/providers/wm_*
+kivy/input/providers/mactouch*
+kivy/input/providers/probesysfs*
+kivy/input/providers/mtdev*
+kivy/input/providers/hidinput*
+kivy/core/camera/camera_videocapture*
+kivy/core/spelling/*osx*
+kivy/core/video/video_pyglet*
+kivy/tools
+kivy/tests/*
+kivy/*/*.h
+kivy/*/*.pxi
+
+# unused encodings
+lib-dynload/*codec*
+encodings/cp*.pyo
+encodings/tis*
+encodings/shift*
+encodings/bz2*
+encodings/iso*
+encodings/undefined*
+encodings/johab*
+encodings/p*
+encodings/m*
+encodings/euc*
+encodings/k*
+encodings/unicode_internal*
+encodings/quo*
+encodings/gb*
+encodings/big5*
+encodings/hp*
+encodings/hz*
+
+# unused python modules
+bsddb/*
+wsgiref/*
+hotshot/*
+pydoc_data/*
+tty.pyo
+anydbm.pyo
+nturl2path.pyo
+LICENCE.txt
+macurl2path.pyo
+dummy_threading.pyo
+audiodev.pyo
+antigravity.pyo
+dumbdbm.pyo
+sndhdr.pyo
+__phello__.foo.pyo
+sunaudio.pyo
+os2emxpath.pyo
+multiprocessing/dummy*
+
+# unused binaries python modules
+lib-dynload/termios.so
+lib-dynload/_lsprof.so
+lib-dynload/*audioop.so
+lib-dynload/_hotshot.so
+lib-dynload/_heapq.so
+lib-dynload/_json.so
+lib-dynload/grp.so
+lib-dynload/resource.so
+lib-dynload/pyexpat.so
+lib-dynload/_ctypes_test.so
+lib-dynload/_testcapi.so
+
+# odd files
+plat-linux3/regen
+
+#>sqlite3
+# conditionnal include depending if some recipes are included or not.
+sqlite3/*
+lib-dynload/_sqlite3.so
+#
+#include
+
+#define LOGI(...) do {} while (0)
+#define LOGE(...) do {} while (0)
+
+#include "android/log.h"
+
+/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */
+
+/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */
+/* #define LOGP(x) LOG("python", (x)) */
+#define LOG_TAG "Python_android"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+
+
+/* Function headers */
+JNIEnv* Android_JNI_GetEnv(void);
+static void Android_JNI_ThreadDestroyed(void*);
+
+static pthread_key_t mThreadKey;
+static JavaVM* mJavaVM;
+
+int Android_JNI_SetupThread(void)
+{
+ Android_JNI_GetEnv();
+ return 1;
+}
+
+/* Library init */
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv *env;
+ mJavaVM = vm;
+ LOGI("JNI_OnLoad called");
+ if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("Failed to get the environment using GetEnv()");
+ return -1;
+ }
+ /*
+ * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
+ * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
+ */
+ if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
+
+ __android_log_print(ANDROID_LOG_ERROR, "pyjniusjni", "Error initializing pthread key");
+ }
+ Android_JNI_SetupThread();
+
+ return JNI_VERSION_1_4;
+}
+
+JNIEnv* Android_JNI_GetEnv(void)
+{
+ /* From http://developer.android.com/guide/practices/jni.html
+ * All threads are Linux threads, scheduled by the kernel.
+ * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
+ * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
+ * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
+ * and cannot make JNI calls.
+ * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
+ * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
+ * is a no-op.
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ */
+
+ JNIEnv *env;
+ int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
+ if(status < 0) {
+ LOGE("failed to attach current thread");
+ return 0;
+ }
+
+ /* From http://developer.android.com/guide/practices/jni.html
+ * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
+ * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
+ * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
+ * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
+ * Note: The destructor is not called unless the stored value is != NULL
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ * (except for some lost CPU cycles)
+ */
+ pthread_setspecific(mThreadKey, (void*) env);
+
+ return env;
+}
+
+static void Android_JNI_ThreadDestroyed(void* value)
+{
+ /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
+ JNIEnv *env = (JNIEnv*) value;
+ if (env != NULL) {
+ (*mJavaVM)->DetachCurrentThread(mJavaVM);
+ pthread_setspecific(mThreadKey, NULL);
+ }
+}
+
+void *WebView_AndroidGetJNIEnv()
+{
+ return Android_JNI_GetEnv();
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 0000000..57112dd
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,330 @@
+package org.kivy.android;
+
+import android.os.SystemClock;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.view.KeyEvent;
+import android.util.Log;
+import android.widget.Toast;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import org.renpy.android.ResourceManager;
+
+public class PythonActivity extends Activity {
+ // This activity is modified from a mixture of the SDLActivity and
+ // PythonActivity in the SDL2 bootstrap, but removing all the SDL2
+ // specifics.
+
+ private static final String TAG = "PythonActivity";
+
+ public static PythonActivity mActivity = null;
+
+ /** If shared libraries (e.g. the native application) could not be loaded. */
+ public static boolean mBrokenLibraries;
+
+ protected static Thread mPythonThread;
+
+ private ResourceManager resourceManager = null;
+ private Bundle mMetaData = null;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
+ public static void initialize() {
+ // The static nature of the singleton and Android quirkiness force us to initialize everything here
+ // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
+ mBrokenLibraries = false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "My oncreate running");
+ resourceManager = new ResourceManager(this);
+
+ Log.v(TAG, "Ready to unpack");
+ File app_root_file = new File(getAppRoot());
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+
+ Log.v(TAG, "About to do super onCreate");
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "Did super onCreate");
+
+ this.mActivity = this;
+ //this.showLoadingScreen();
+ Log.v("Python", "Device: " + android.os.Build.DEVICE);
+ Log.v("Python", "Model: " + android.os.Build.MODEL);
+
+ //Log.v(TAG, "Ready to unpack");
+ //new UnpackFilesTask().execute(getAppRoot());
+
+ PythonActivity.initialize();
+
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+ loadLibraries();
+ } catch(UnsatisfiedLinkError e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ }
+
+ if (mBrokenLibraries)
+ {
+ AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
+ dlgAlert.setMessage("An error occurred while trying to load the application libraries. Please try again and/or reinstall."
+ + System.getProperty("line.separator")
+ + System.getProperty("line.separator")
+ + "Error: " + errorMsgBrokenLib);
+ dlgAlert.setTitle("Python Error");
+ dlgAlert.setPositiveButton("Exit",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,int id) {
+ // if this button is clicked, close current activity
+ PythonActivity.mActivity.finish();
+ }
+ });
+ dlgAlert.setCancelable(false);
+ dlgAlert.create().show();
+
+ return;
+ }
+
+ // Set up the Python environment
+ String app_root_dir = getAppRoot();
+ String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
+
+ Log.v(TAG, "Setting env vars for start.c and Python to use");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
+
+ try {
+ Log.v(TAG, "Access to our meta-data...");
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ final Thread pythonThread = new Thread(new PythonMain(), "PythonThread");
+ PythonActivity.mPythonThread = pythonThread;
+ pythonThread.start();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("Destroy", "end of app");
+ super.onDestroy();
+
+ // make sure all child threads (python_thread) are stopped
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ public void loadLibraries() {
+ String app_root = new String(getAppRoot());
+ File app_root_file = new File(app_root);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
+ }
+
+ long lastBackClick = 0;
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Check if the key event was the Back button
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // If there's no web page history, bubble up to the default
+ // system behavior (probably exit the activity)
+ if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ lastBackClick = SystemClock.elapsedRealtime();
+ Toast.makeText(this, "Tap again to close the app", Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ lastBackClick = SystemClock.elapsedRealtime();
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onNewIntent
+ //
+
+ public interface NewIntentListener {
+ void onNewIntent(Intent intent);
+ }
+
+ private List newIntentListeners = null;
+
+ public void registerNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ this.newIntentListeners = Collections.synchronizedList(new ArrayList());
+ this.newIntentListeners.add(listener);
+ }
+
+ public void unregisterNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.newIntentListeners.remove(listener);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.newIntentListeners ) {
+ Iterator iterator = this.newIntentListeners.iterator();
+ while ( iterator.hasNext() ) {
+ (iterator.next()).onNewIntent(intent);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onActivityResult
+ //
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ private List activityResultListeners = null;
+
+ public void registerActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ this.activityResultListeners = Collections.synchronizedList(new ArrayList());
+ this.activityResultListeners.add(listener);
+ }
+
+ public void unregisterActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.activityResultListeners.remove(listener);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.activityResultListeners ) {
+ Iterator iterator = this.activityResultListeners.iterator();
+ while ( iterator.hasNext() )
+ (iterator.next()).onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
+ serviceIntent.putExtra("androidPrivate", argument);
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
+ serviceIntent.putExtra("serviceTitle", serviceTitle);
+ serviceIntent.putExtra("serviceDescription", serviceDescription);
+ serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ PythonActivity.mActivity.startService(serviceIntent);
+ }
+
+ public static void stop_service() {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ PythonActivity.mActivity.stopService(serviceIntent);
+ }
+
+
+ public static native void nativeSetenv(String name, String value);
+ public static native int nativeInit(Object arguments);
+
+}
+
+
+class PythonMain implements Runnable {
+ @Override
+ public void run() {
+ PythonActivity.nativeInit(new String[0]);
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/src/main/res/mipmap/.gitkeep b/p4a/pythonforandroid/bootstraps/service_only/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 0000000..d19ed32
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,101 @@
+
+
+
+ = 9 %}
+ android:xlargeScreens="true"
+ {% endif %}
+ />
+
+
+
+
+
+ {% for perm in args.permissions %}
+ {% if '.' in perm %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+ {% if args.wakelock %}
+
+ {% endif %}
+
+ {% if args.billing_pubkey %}
+
+ {% endif %}
+
+
+
+ {% for l in args.android_used_libs %}
+
+ {% endfor %}
+ {% for m in args.meta_data %}
+ {% endfor %}
+
+
+
+
+
+
+
+ {%- if args.intent_filters -%}
+ {{- args.intent_filters -}}
+ {%- endif -%}
+
+
+ {% if service %}
+
+ {% endif %}
+ {% for name in service_names %}
+
+ {% endfor %}
+
+ {% if args.billing_pubkey %}
+
+
+
+
+
+
+
+
+ {% endif %}
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
new file mode 100644
index 0000000..598549d
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
@@ -0,0 +1,70 @@
+package {{ args.package }};
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.content.Intent;
+import android.content.Context;
+import org.kivy.android.PythonService;
+
+public class Service{{ name|capitalize }} extends PythonService {
+ /**
+ * Binder given to clients
+ */
+ private final IBinder mBinder = new Service{{ name|capitalize }}Binder();
+
+ {% if sticky %}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int startType() {
+ return START_STICKY;
+ }
+ {% endif %}
+
+ @Override
+ protected int getServiceId() {
+ return {{ service_id }};
+ }
+
+ public static void start(Context ctx, String pythonServiceArgument) {
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ intent.putExtra("androidPrivate", argument);
+ intent.putExtra("androidArgument", argument);
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("serviceTitle", "{{ name|capitalize }}");
+ intent.putExtra("serviceDescription", "");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
+ intent.putExtra("pythonHome", argument);
+ intent.putExtra("androidUnpack", argument);
+ intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ ctx.startService(intent);
+ }
+
+ public static void stop(Context ctx) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ ctx.stopService(intent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Class used for the client Binder. Because we know this service always
+ * runs in the same process as its clients, we don't need to deal with IPC.
+ */
+ public class Service{{ name|capitalize }}Binder extends Binder {
+ Service{{ name|capitalize }} getService() {
+ // Return this instance of Service{{ name|capitalize }} so clients can call public methods
+ return Service{{ name|capitalize }}.this;
+ }
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml b/p4a/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml
new file mode 100644
index 0000000..2286657
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml
@@ -0,0 +1,5 @@
+
+
+ {{ args.name }}
+ {{ private_version }}
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/__init__.py b/p4a/pythonforandroid/bootstraps/webview/__init__.py
new file mode 100644
index 0000000..da33ac7
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/__init__.py
@@ -0,0 +1,49 @@
+from pythonforandroid.toolchain import Bootstrap, current_directory, info, info_main, shprint
+from pythonforandroid.util import ensure_dir
+from os.path import join
+import sh
+
+
+class WebViewBootstrap(Bootstrap):
+ name = 'webview'
+
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
+
+ def assemble_distribution(self):
+ info_main('# Creating Android project from build and {} bootstrap'.format(
+ self.name))
+
+ shprint(sh.rm, '-rf', self.dist_dir)
+ shprint(sh.cp, '-r', self.build_dir, self.dist_dir)
+ with current_directory(self.dist_dir):
+ with open('local.properties', 'w') as fileh:
+ fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
+
+ with current_directory(self.dist_dir):
+ info('Copying python distribution')
+
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
+
+ for arch in self.ctx.archs:
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ self.distribute_aars(arch)
+
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+
+ if 'sqlite3' not in self.ctx.recipe_build_order:
+ with open('blacklist.txt', 'a') as fileh:
+ fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
+
+ super().assemble_distribution()
+
+
+bootstrap = WebViewBootstrap()
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/blacklist.txt b/p4a/pythonforandroid/bootstraps/webview/build/blacklist.txt
new file mode 100644
index 0000000..53cc634
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/blacklist.txt
@@ -0,0 +1,91 @@
+# prevent user to include invalid extensions
+*.apk
+*.aab
+*.apks
+*.pxd
+
+# eggs
+*.egg-info
+
+# unit test
+unittest/*
+
+# python config
+config/makesetup
+
+# unused kivy files (platform specific)
+kivy/input/providers/wm_*
+kivy/input/providers/mactouch*
+kivy/input/providers/probesysfs*
+kivy/input/providers/mtdev*
+kivy/input/providers/hidinput*
+kivy/core/camera/camera_videocapture*
+kivy/core/spelling/*osx*
+kivy/core/video/video_pyglet*
+kivy/tools
+kivy/tests/*
+kivy/*/*.h
+kivy/*/*.pxi
+
+# unused encodings
+lib-dynload/*codec*
+encodings/cp*.pyo
+encodings/tis*
+encodings/shift*
+encodings/bz2*
+encodings/iso*
+encodings/undefined*
+encodings/johab*
+encodings/p*
+encodings/m*
+encodings/euc*
+encodings/k*
+encodings/unicode_internal*
+encodings/quo*
+encodings/gb*
+encodings/big5*
+encodings/hp*
+encodings/hz*
+
+# unused python modules
+bsddb/*
+wsgiref/*
+hotshot/*
+pydoc_data/*
+tty.pyo
+anydbm.pyo
+nturl2path.pyo
+LICENCE.txt
+macurl2path.pyo
+dummy_threading.pyo
+audiodev.pyo
+antigravity.pyo
+dumbdbm.pyo
+sndhdr.pyo
+__phello__.foo.pyo
+sunaudio.pyo
+os2emxpath.pyo
+multiprocessing/dummy*
+
+# unused binaries python modules
+lib-dynload/termios.so
+lib-dynload/_lsprof.so
+lib-dynload/*audioop.so
+lib-dynload/_hotshot.so
+lib-dynload/_heapq.so
+lib-dynload/_json.so
+lib-dynload/grp.so
+lib-dynload/resource.so
+lib-dynload/pyexpat.so
+lib-dynload/_ctypes_test.so
+lib-dynload/_testcapi.so
+
+# odd files
+plat-linux3/regen
+
+#>sqlite3
+# conditionnal include depending if some recipes are included or not.
+sqlite3/*
+lib-dynload/_sqlite3.so
+#
+#include
+
+#define LOGI(...) do {} while (0)
+#define LOGE(...) do {} while (0)
+
+#include "android/log.h"
+
+/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */
+
+/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */
+/* #define LOGP(x) LOG("python", (x)) */
+#define LOG_TAG "Python_android"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+
+
+/* Function headers */
+JNIEnv* Android_JNI_GetEnv(void);
+static void Android_JNI_ThreadDestroyed(void*);
+
+static pthread_key_t mThreadKey;
+static JavaVM* mJavaVM;
+
+int Android_JNI_SetupThread(void)
+{
+ Android_JNI_GetEnv();
+ return 1;
+}
+
+/* Library init */
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv *env;
+ mJavaVM = vm;
+ LOGI("JNI_OnLoad called");
+ if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("Failed to get the environment using GetEnv()");
+ return -1;
+ }
+ /*
+ * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
+ * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
+ */
+ if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
+
+ __android_log_print(ANDROID_LOG_ERROR, "pyjniusjni", "Error initializing pthread key");
+ }
+ Android_JNI_SetupThread();
+
+ return JNI_VERSION_1_4;
+}
+
+JNIEnv* Android_JNI_GetEnv(void)
+{
+ /* From http://developer.android.com/guide/practices/jni.html
+ * All threads are Linux threads, scheduled by the kernel.
+ * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
+ * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
+ * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
+ * and cannot make JNI calls.
+ * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
+ * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
+ * is a no-op.
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ */
+
+ JNIEnv *env;
+ int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
+ if(status < 0) {
+ LOGE("failed to attach current thread");
+ return 0;
+ }
+
+ /* From http://developer.android.com/guide/practices/jni.html
+ * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
+ * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
+ * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
+ * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
+ * Note: The destructor is not called unless the stored value is != NULL
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ * (except for some lost CPU cycles)
+ */
+ pthread_setspecific(mThreadKey, (void*) env);
+
+ return env;
+}
+
+static void Android_JNI_ThreadDestroyed(void* value)
+{
+ /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
+ JNIEnv *env = (JNIEnv*) value;
+ if (env != NULL) {
+ (*mJavaVM)->DetachCurrentThread(mJavaVM);
+ pthread_setspecific(mThreadKey, NULL);
+ }
+}
+
+void *WebView_AndroidGetJNIEnv()
+{
+ return Android_JNI_GetEnv();
+}
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/proguard-project.txt b/p4a/pythonforandroid/bootstraps/webview/build/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/webview/build/src/main/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
new file mode 100644
index 0000000..58a1c5e
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
@@ -0,0 +1,19 @@
+package org.kivy.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.Context;
+
+public class GenericBroadcastReceiver extends BroadcastReceiver {
+
+ GenericBroadcastReceiverCallback listener;
+
+ public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {
+ super();
+ this.listener = listener;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ this.listener.onReceive(context, intent);
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
new file mode 100644
index 0000000..1a87c98
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
@@ -0,0 +1,8 @@
+package org.kivy.android;
+
+import android.content.Intent;
+import android.content.Context;
+
+public interface GenericBroadcastReceiverCallback {
+ void onReceive(Context context, Intent intent);
+};
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 0000000..2f0afdc
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,572 @@
+package org.kivy.android;
+
+import android.os.SystemClock;
+
+import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Toast;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.widget.ImageView;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+
+import android.widget.AbsoluteLayout;
+import android.view.ViewGroup.LayoutParams;
+
+import android.webkit.WebBackForwardList;
+import android.webkit.WebViewClient;
+import android.webkit.WebView;
+import android.webkit.CookieManager;
+import android.net.Uri;
+
+import org.renpy.android.ResourceManager;
+
+public class PythonActivity extends Activity {
+ // This activity is modified from a mixture of the SDLActivity and
+ // PythonActivity in the SDL2 bootstrap, but removing all the SDL2
+ // specifics.
+
+ private static final String TAG = "PythonActivity";
+
+ public static PythonActivity mActivity = null;
+ public static boolean mOpenExternalLinksInBrowser = false;
+
+ /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
+ public static boolean mBrokenLibraries;
+
+ protected static ViewGroup mLayout;
+ protected static WebView mWebView;
+
+ protected static Thread mPythonThread;
+
+ private ResourceManager resourceManager = null;
+ private Bundle mMetaData = null;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
+ public static void initialize() {
+ // The static nature of the singleton and Android quirkyness force us to initialize everything here
+ // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
+ mWebView = null;
+ mLayout = null;
+ mBrokenLibraries = false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "My oncreate running");
+ resourceManager = new ResourceManager(this);
+ super.onCreate(savedInstanceState);
+
+ this.mActivity = this;
+ this.showLoadingScreen();
+ new UnpackFilesTask().execute(getAppRoot());
+ }
+
+ private class UnpackFilesTask extends AsyncTask {
+ @Override
+ protected String doInBackground(String... params) {
+ File app_root_file = new File(params[0]);
+ Log.v(TAG, "Ready to unpack");
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ Log.v("Python", "Device: " + android.os.Build.DEVICE);
+ Log.v("Python", "Model: " + android.os.Build.MODEL);
+
+ PythonActivity.initialize();
+
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+ loadLibraries();
+ } catch(UnsatisfiedLinkError e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ }
+
+ if (mBrokenLibraries)
+ {
+ AlertDialog.Builder dlgAlert = new AlertDialog.Builder(PythonActivity.mActivity);
+ dlgAlert.setMessage("An error occurred while trying to load the application libraries. Please try again and/or reinstall."
+ + System.getProperty("line.separator")
+ + System.getProperty("line.separator")
+ + "Error: " + errorMsgBrokenLib);
+ dlgAlert.setTitle("Python Error");
+ dlgAlert.setPositiveButton("Exit",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,int id) {
+ // if this button is clicked, close current activity
+ PythonActivity.mActivity.finish();
+ }
+ });
+ dlgAlert.setCancelable(false);
+ dlgAlert.create().show();
+
+ return;
+ }
+
+ // Set up the webview
+ String app_root_dir = getAppRoot();
+
+ mWebView = new WebView(PythonActivity.mActivity);
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.getSettings().setDomStorageEnabled(true);
+ mWebView.loadUrl("file:///android_asset/_load.html");
+
+ mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+ mWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ Uri u = Uri.parse(url);
+ if (mOpenExternalLinksInBrowser) {
+ if (!(u.getScheme().equals("file") || u.getHost().equals("127.0.0.1"))) {
+ Intent i = new Intent(Intent.ACTION_VIEW, u);
+ startActivity(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ CookieManager.getInstance().flush();
+ }
+ });
+ mLayout = new AbsoluteLayout(PythonActivity.mActivity);
+ mLayout.addView(mWebView);
+
+ setContentView(mLayout);
+
+ String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
+
+ Log.v(TAG, "Setting env vars for start.c and Python to use");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
+
+ try {
+ Log.v(TAG, "Access to our meta-data...");
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ final Thread pythonThread = new Thread(new PythonMain(), "PythonThread");
+ PythonActivity.mPythonThread = pythonThread;
+ pythonThread.start();
+
+ final Thread wvThread = new Thread(new WebViewLoaderMain(), "WvThread");
+ wvThread.start();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("Destroy", "end of app");
+ super.onDestroy();
+
+ // make sure all child threads (python_thread) are stopped
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ public void loadLibraries() {
+ String app_root = new String(getAppRoot());
+ File app_root_file = new File(app_root);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
+ }
+
+ public static void loadUrl(String url) {
+ class LoadUrl implements Runnable {
+ private String mUrl;
+
+ public LoadUrl(String url) {
+ mUrl = url;
+ }
+
+ public void run() {
+ mWebView.loadUrl(mUrl);
+ }
+ }
+
+ Log.i(TAG, "Opening URL: " + url);
+ mActivity.runOnUiThread(new LoadUrl(url));
+ }
+
+ public static void enableZoom() {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.getSettings().setBuiltInZoomControls(true);
+ mWebView.getSettings().setDisplayZoomControls(false);
+ }
+ });
+ }
+
+ public static ViewGroup getLayout() {
+ return mLayout;
+ }
+
+ long lastBackClick = 0;
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Check if the key event was the Back button
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Go back if there is web page history behind,
+ // but not to the start preloader
+ WebBackForwardList webViewBackForwardList = mWebView.copyBackForwardList();
+ if (webViewBackForwardList.getCurrentIndex() > 1) {
+ mWebView.goBack();
+ return true;
+ }
+
+ // If there's no web page history, bubble up to the default
+ // system behavior (probably exit the activity)
+ if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ lastBackClick = SystemClock.elapsedRealtime();
+ Toast.makeText(this, "Tap again to close the app", Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ lastBackClick = SystemClock.elapsedRealtime();
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ // loading screen implementation
+ public static ImageView mImageView = null;
+ public void removeLoadingScreen() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (PythonActivity.mImageView != null &&
+ PythonActivity.mImageView.getParent() != null) {
+ ((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
+ PythonActivity.mImageView);
+ PythonActivity.mImageView = null;
+ }
+ }
+ });
+ }
+
+ protected void showLoadingScreen() {
+ // load the bitmap
+ // 1. if the image is valid and we don't have layout yet, assign this bitmap
+ // as main view.
+ // 2. if we have a layout, just set it in the layout.
+ // 3. If we have an mImageView already, then do nothing because it will have
+ // already been made the content view or added to the layout.
+
+ if (mImageView == null) {
+ int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
+ InputStream is = this.getResources().openRawResource(presplashId);
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {};
+ }
+
+ mImageView = new ImageView(this);
+ mImageView.setImageBitmap(bitmap);
+
+ /*
+ * Set the presplash loading screen background color
+ * https://developer.android.com/reference/android/graphics/Color.html
+ * Parse the color string, and return the corresponding color-int.
+ * If the string cannot be parsed, throws an IllegalArgumentException exception.
+ * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+ * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
+ * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
+ * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
+ */
+ String backgroundColor = resourceManager.getString("presplash_color");
+ if (backgroundColor != null) {
+ try {
+ mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
+ } catch (IllegalArgumentException e) {}
+ }
+ mImageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+
+ }
+
+ if (mLayout == null) {
+ setContentView(mImageView);
+ } else if (PythonActivity.mImageView.getParent() == null){
+ mLayout.addView(mImageView);
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onNewIntent
+ //
+
+ public interface NewIntentListener {
+ void onNewIntent(Intent intent);
+ }
+
+ private List newIntentListeners = null;
+
+ public void registerNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ this.newIntentListeners = Collections.synchronizedList(new ArrayList());
+ this.newIntentListeners.add(listener);
+ }
+
+ public void unregisterNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.newIntentListeners.remove(listener);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.newIntentListeners ) {
+ Iterator iterator = this.newIntentListeners.iterator();
+ while ( iterator.hasNext() ) {
+ (iterator.next()).onNewIntent(intent);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onActivityResult
+ //
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ private List activityResultListeners = null;
+
+ public void registerActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ this.activityResultListeners = Collections.synchronizedList(new ArrayList());
+ this.activityResultListeners.add(listener);
+ }
+
+ public void unregisterActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.activityResultListeners.remove(listener);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.activityResultListeners ) {
+ Iterator iterator = this.activityResultListeners.iterator();
+ while ( iterator.hasNext() )
+ (iterator.next()).onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
+ serviceIntent.putExtra("androidPrivate", argument);
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
+ serviceIntent.putExtra("serviceTitle", serviceTitle);
+ serviceIntent.putExtra("serviceDescription", serviceDescription);
+ serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ PythonActivity.mActivity.startService(serviceIntent);
+ }
+
+ public static void stop_service() {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ PythonActivity.mActivity.stopService(serviceIntent);
+ }
+
+
+ public static native void nativeSetenv(String name, String value);
+ public static native int nativeInit(Object arguments);
+
+
+ /**
+ * Used by android.permissions p4a module to register a call back after
+ * requesting runtime permissions
+ **/
+ public interface PermissionsCallback {
+ void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
+ }
+
+ private PermissionsCallback permissionCallback;
+ private boolean havePermissionsCallback = false;
+
+ public void addPermissionsCallback(PermissionsCallback callback) {
+ permissionCallback = callback;
+ havePermissionsCallback = true;
+ Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult");
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ Log.v(TAG, "onRequestPermissionsResult()");
+ if (havePermissionsCallback) {
+ Log.v(TAG, "onRequestPermissionsResult passed to callback");
+ permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /**
+ * Used by android.permissions p4a module to check a permission
+ **/
+ public boolean checkCurrentPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return true;
+
+ try {
+ java.lang.reflect.Method methodCheckPermission =
+ Activity.class.getMethod("checkSelfPermission", String.class);
+ Object resultObj = methodCheckPermission.invoke(this, permission);
+ int result = Integer.parseInt(resultObj.toString());
+ if (result == PackageManager.PERMISSION_GRANTED)
+ return true;
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Used by android.permissions p4a module to request runtime permissions
+ **/
+ public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return;
+ try {
+ java.lang.reflect.Method methodRequestPermission =
+ Activity.class.getMethod("requestPermissions",
+ String[].class, int.class);
+ methodRequestPermission.invoke(this, permissions, requestCode);
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ }
+
+ public void requestPermissions(String[] permissions) {
+ requestPermissionsWithRequestCode(permissions, 1);
+ }
+}
+
+
+class PythonMain implements Runnable {
+ @Override
+ public void run() {
+ PythonActivity.nativeInit(new String[0]);
+ }
+}
+
+class WebViewLoaderMain implements Runnable {
+ @Override
+ public void run() {
+ WebViewLoader.testConnection();
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroid/bootstraps/webview/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/icon.png b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..59a00ba
Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/icon.png differ
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml
new file mode 100644
index 0000000..123c4b6
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap-anydpi-v26/.gitkeep b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap-anydpi-v26/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap/.gitkeep b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml
new file mode 100644
index 0000000..daebceb
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ SDL App
+ 0.1
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 0000000..f77533b
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,108 @@
+
+
+
+ = 9 %}
+ android:xlargeScreens="true"
+ {% endif %}
+ />
+
+
+
+
+
+
+ {% for perm in args.permissions %}
+ {% if '.' in perm %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+ {% if args.wakelock %}
+
+ {% endif %}
+
+ {% if args.billing_pubkey %}
+
+ {% endif %}
+
+
+
+ {% for l in args.android_used_libs %}
+
+ {% endfor %}
+ {% for m in args.meta_data %}
+ {% endfor %}
+
+
+
+
+
+
+
+ {%- if args.intent_filters -%}
+ {{- args.intent_filters -}}
+ {%- endif -%}
+
+
+ {% if service %}
+
+ {% endif %}
+ {% for name in service_names %}
+
+ {% endfor %}
+
+ {% if args.billing_pubkey %}
+
+
+
+
+
+
+
+
+ {% endif %}
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java b/p4a/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java
new file mode 100644
index 0000000..5482da8
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java
@@ -0,0 +1,56 @@
+package org.kivy.android;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.InetSocketAddress;
+
+import android.os.SystemClock;
+
+import android.os.Handler;
+
+import org.kivy.android.PythonActivity;
+
+public class WebViewLoader {
+ private static final String TAG = "WebViewLoader";
+
+ public static void testConnection() {
+
+ while (true) {
+ if (WebViewLoader.pingHost("localhost", {{ args.port }}, 100)) {
+ Log.v(TAG, "Successfully pinged localhost:{{ args.port }}");
+ Handler mainHandler = new Handler(PythonActivity.mActivity.getMainLooper());
+ Runnable myRunnable = new Runnable() {
+ @Override
+ public void run() {
+ PythonActivity.mActivity.loadUrl("http://127.0.0.1:{{ args.port }}/");
+ Log.v(TAG, "Loaded webserver in webview");
+ }
+ };
+ mainHandler.post(myRunnable);
+ break;
+
+ } else {
+ Log.v(TAG, "Could not ping localhost:{{ args.port }}");
+ try {
+ Thread.sleep(100);
+ } catch(InterruptedException e) {
+ Log.v(TAG, "InterruptedException occurred when sleeping");
+ }
+ }
+ }
+ }
+
+ public static boolean pingHost(String host, int port, int timeout) {
+ Socket socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(host, port), timeout);
+ socket.close();
+ return true;
+ } catch (IOException e) {
+ try {socket.close();} catch (IOException f) {return false;}
+ return false; // Either timeout or unreachable or failed DNS lookup.
+ }
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml b/p4a/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml
new file mode 100644
index 0000000..41c20ac
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml
@@ -0,0 +1,6 @@
+
+
+ {{ args.name }}
+ {{ private_version }}
+ {{ args.presplash_color }}
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.tmpl.xml b/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.tmpl.xml
new file mode 100644
index 0000000..9564aae
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.tmpl.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.xml.tmpl b/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.xml.tmpl
new file mode 100644
index 0000000..9564aae
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/templates/test/build.xml.tmpl
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html b/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html
new file mode 100644
index 0000000..1896d63
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Python WebView loader
+
+
+
+
+
Loading...
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_loading_style.css b/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_loading_style.css
new file mode 100644
index 0000000..6833855
--- /dev/null
+++ b/p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_loading_style.css
@@ -0,0 +1,78 @@
+
+h1 {
+ font-size: 30px;
+ color: blue;
+ font-weight: bold;
+ text-align:center;
+}
+
+h2 {
+ text-align:center;
+}
+
+button {
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+ margin-top: 50px;
+ font-size: 30px;
+}
+
+
+/* Loader from http://projects.lukehaas.me/css-loaders/#load1 */
+
+.loader,
+.loader:before,
+.loader:after {
+ background: #aaaaff;
+ -webkit-animation: load1 1s infinite ease-in-out;
+ animation: load1 1s infinite ease-in-out;
+ width: 1em;
+ height: 4em;
+}
+.loader:before,
+.loader:after {
+ position: absolute;
+ top: 0;
+ content: '';
+}
+.loader:before {
+ left: -1.5em;
+}
+.loader {
+ text-indent: -9999em;
+ margin: 8em auto;
+ position: relative;
+ font-size: 11px;
+ -webkit-animation-delay: -0.16s;
+ animation-delay: -0.16s;
+}
+.loader:after {
+ left: 1.5em;
+ -webkit-animation-delay: -0.32s;
+ animation-delay: -0.32s;
+}
+@-webkit-keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #aaaaff;
+ height: 4em;
+ }
+ 40% {
+ box-shadow: 0 -2em #aaaaff;
+ height: 5em;
+ }
+}
+@keyframes load1 {
+ 0%,
+ 80%,
+ 100% {
+ box-shadow: 0 0 #aaaaff;
+ height: 4em;
+ }
+ 40% {
+ box-shadow: 0 -2em #aaaaff;
+ height: 5em;
+ }
+}
diff --git a/p4a/pythonforandroid/build.py b/p4a/pythonforandroid/build.py
index 374929d..4cda951 100644
--- a/p4a/pythonforandroid/build.py
+++ b/p4a/pythonforandroid/build.py
@@ -1,32 +1,66 @@
-from __future__ import print_function
-
-from os.path import (join, realpath, dirname, expanduser, exists,
- split, isdir)
+from os.path import (
+ abspath, join, realpath, dirname, expanduser, exists
+)
from os import environ
import copy
import os
import glob
-import sys
import re
import sh
+import shutil
import subprocess
+from contextlib import suppress
from pythonforandroid.util import (
- current_directory, ensure_dir, get_virtualenv_executable,
- BuildInterruptingException
+ current_directory, ensure_dir,
+ 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.pythonpackage import get_package_name
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)
+from pythonforandroid.androidndk import AndroidNDK
-class Context(object):
+def get_targets(sdk_dir):
+ if exists(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager')):
+ avdmanager = sh.Command(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager'))
+ targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
+
+ elif exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
+ avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
+ targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
+ elif exists(join(sdk_dir, 'tools', 'android')):
+ android = sh.Command(join(sdk_dir, 'tools', 'android'))
+ targets = android('list').stdout.decode('utf-8').split('\n')
+ else:
+ raise BuildInterruptingException(
+ 'Could not find `android` or `sdkmanager` binaries in Android SDK',
+ instructions='Make sure the path to the Android SDK is correct')
+ return targets
+
+
+def get_available_apis(sdk_dir):
+ targets = get_targets(sdk_dir)
+ 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]
+ return apis
+
+
+class Context:
'''A build context. If anything will be built, an instance this class
will be instantiated and used to hold all the build state.'''
+ # Whether to make a debug or release build
+ build_as_debuggable = False
+
+ # Whether to strip debug symbols in `.so` files
+ with_debug_symbols = False
+
env = environ.copy()
# the filepath of toolchain.py
root_dir = None
@@ -36,25 +70,31 @@ class Context(object):
# in which bootstraps are copied for building
# and recipes are built
build_dir = None
+
+ distribution = None
+ """The Distribution object representing the current build target location."""
+
# 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
+ # Whether setup.py or similar should be used if present:
+ use_setup_py = False
+
ccache = None # whether to use ccache
- cython = None # the cython interpreter name
- ndk_platform = None # the ndk platform directory
+ ndk = None
- dist_name = None # should be deprecated in favour of self.dist.dist_name
bootstrap = None
bootstrap_build_dir = None
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_bootstrap_files = False # If True, will symlink instead of copying during build
java_build_tool = 'auto'
@@ -70,34 +110,33 @@ class Context(object):
@property
def libs_dir(self):
# Was previously hardcoded as self.build_dir/libs
- dir = join(self.build_dir, 'libs_collections',
- self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'libs_collections',
+ self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def javaclass_dir(self):
# Was previously hardcoded as self.build_dir/java
- dir = join(self.build_dir, 'javaclasses',
- self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'javaclasses',
+ self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def aars_dir(self):
- dir = join(self.build_dir, 'aars', self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'aars', self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def python_installs_dir(self):
- dir = join(self.build_dir, 'python-installs')
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'python-installs')
+ ensure_dir(directory)
+ return directory
- def get_python_install_dir(self):
- dir = join(self.python_installs_dir, self.bootstrap.distribution.name)
- return dir
+ def get_python_install_dir(self, arch):
+ return join(self.python_installs_dir, self.bootstrap.distribution.name, arch)
def setup_dirs(self, storage_dir):
'''Calculates all the storage and build dirs, and makes sure
@@ -185,8 +224,6 @@ class Context(object):
if self._build_env_prepared:
return
- ok = True
-
# Work out where the Android SDK is
sdk_dir = None
if user_sdk_dir:
@@ -202,10 +239,10 @@ class Context(object):
possible_dirs = glob.glob(expanduser(join(
'~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
possible_dirs = [d for d in possible_dirs if not
- (d.endswith('.bz2') or d.endswith('.gz'))]
+ d.endswith(('.bz2', '.gz'))]
if possible_dirs:
info('Found possible SDK dirs in buildozer dir: {}'.format(
- ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))
info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
warning('This SDK lookup is intended for debug only, if you '
'use python-for-android much you should probably '
@@ -230,21 +267,10 @@ class Context(object):
android_api = int(android_api)
self.android_api = android_api
- 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'))
- targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
- elif exists(join(sdk_dir, 'tools', 'android')):
- android = sh.Command(join(sdk_dir, 'tools', 'android'))
- targets = android('list').stdout.decode('utf-8').split('\n')
- else:
- 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]
+ for arch in self.archs:
+ # Maybe We could remove this one in a near future (ARMv5 is definitely old)
+ check_target_api(android_api, arch)
+ apis = get_available_apis(self.sdk_dir)
info('Available Android APIs are ({})'.format(
', '.join(map(str, apis))))
if android_api in apis:
@@ -279,7 +305,7 @@ class Context(object):
'~', '.buildozer', 'android', 'platform', 'android-ndk-r*')))
if possible_dirs:
info('Found possible NDK dirs in buildozer dir: {}'.format(
- ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))
info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
warning('This NDK lookup is intended for debug only, if you '
'use python-for-android much you should probably '
@@ -288,11 +314,8 @@ class Context(object):
if ndk_dir is None:
raise BuildInterruptingException('Android NDK dir was not specified')
self.ndk_dir = realpath(ndk_dir)
-
check_ndk_version(ndk_dir)
- self.ndk = 'crystax' # force crystax detection
-
ndk_api = None
if user_ndk_api:
ndk_api = user_ndk_api
@@ -310,103 +333,32 @@ class Context(object):
check_ndk_api(ndk_api, self.android_api)
- virtualenv = get_virtualenv_executable()
- if virtualenv is None:
- raise IOError('Couldn\'t find a virtualenv executable, '
- 'you must install this to use p4a.')
- self.virtualenv = virtualenv
- info('Found virtualenv at {}'.format(virtualenv))
+ self.ndk = AndroidNDK(self.ndk_dir)
# path to some tools
- self.ccache = sh.which("ccache")
+ self.ccache = shutil.which("ccache")
if not self.ccache:
info('ccache is missing, the build will not be optimized in the '
'future.')
- for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"):
- cython = sh.which(cython_fn)
- if cython:
- self.cython = cython
- break
- else:
- raise BuildInterruptingException('No cython binary found.')
- if not self.cython:
- ok = False
- warning("Missing requirement: cython is not installed")
+ try:
+ subprocess.check_output([
+ "python3", "-m", "cython", "--help",
+ ])
+ except subprocess.CalledProcessError:
+ warning('Cython for python3 missing. If you are building for '
+ ' a python 3 target (which is the default)'
+ ' then THINGS WILL BREAK.')
- # This would need to be changed if supporting multiarch APKs
- arch = self.archs[0]
- platform_dir = arch.platform_dir
- toolchain_prefix = arch.toolchain_prefix
- toolchain_version = None
- self.ndk_platform = join(
- self.ndk_dir,
- 'platforms',
- 'android-{}'.format(self.ndk_api),
- platform_dir)
- if not exists(self.ndk_platform):
- warning('ndk_platform doesn\'t exist: {}'.format(
- self.ndk_platform))
- ok = False
-
- py_platform = sys.platform
- if py_platform in ['linux2', 'linux3']:
- py_platform = 'linux'
-
- toolchain_versions = []
- toolchain_path = join(self.ndk_dir, 'toolchains')
- if isdir(toolchain_path):
- toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
- toolchain_prefix))
- toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
- for path in toolchain_contents]
- else:
- warning('Could not find toolchain subdirectory!')
- ok = False
- toolchain_versions.sort()
-
- toolchain_versions_gcc = []
- for toolchain_version in toolchain_versions:
- if toolchain_version[0].isdigit():
- # GCC toolchains begin with a number
- toolchain_versions_gcc.append(toolchain_version)
-
- if toolchain_versions:
- info('Found the following toolchain versions: {}'.format(
- toolchain_versions))
- info('Picking the latest gcc toolchain, here {}'.format(
- toolchain_versions_gcc[-1]))
- toolchain_version = toolchain_versions_gcc[-1]
- else:
- warning('Could not find any toolchain for {}!'.format(
- toolchain_prefix))
- ok = False
-
- self.toolchain_prefix = toolchain_prefix
- self.toolchain_version = toolchain_version
- # Modify the path so that sh finds modules appropriately
- environ['PATH'] = (
- '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
- 'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
- '{toolchain_prefix}-{toolchain_version}/prebuilt/'
- '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
- 'tools:{path}').format(
- sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir,
- toolchain_prefix=toolchain_prefix,
- toolchain_version=toolchain_version,
- py_platform=py_platform, path=environ.get('PATH'))
-
- for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
- "tar", "bzip2", "unzip", "make", "gcc", "g++"):
- if not sh.which(executable):
- warning("Missing executable: {} is not installed".format(
- executable))
-
- if not ok:
- raise BuildInterruptingException(
- 'python-for-android cannot continue due to the missing executables above')
+ self.env["PATH"] = ":".join(
+ [
+ self.ndk.llvm_bin_dir,
+ self.ndk_dir,
+ f"{self.sdk_dir}/tools",
+ environ.get("PATH"),
+ ]
+ )
def __init__(self):
- super(Context, self).__init__()
self.include_dirs = []
self._build_env_prepared = False
@@ -417,12 +369,12 @@ class Context(object):
self._ndk_api = None
self.ndk = None
- self.toolchain_prefix = None
- self.toolchain_version = None
-
self.local_recipes = None
self.copy_libs = False
+ self.activity_class_name = u'org.kivy.android.PythonActivity'
+ self.service_class_name = u'org.kivy.android.PythonService'
+
# this list should contain all Archs, it is pruned later
self.archs = (
ArchARM(self),
@@ -452,26 +404,24 @@ class Context(object):
if not self.archs:
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])))
+ ', '.join(arch.arch for arch in self.archs)))
- def prepare_bootstrap(self, bs):
- bs.ctx = self
- self.bootstrap = bs
+ def prepare_bootstrap(self, bootstrap):
+ if not bootstrap:
+ raise TypeError("None is not allowed for bootstrap")
+ bootstrap.ctx = self
+ self.bootstrap = bootstrap
self.bootstrap.prepare_build_dir()
self.bootstrap_build_dir = self.bootstrap.build_dir
- def prepare_dist(self, name):
- self.dist_name = name
- self.bootstrap.prepare_dist_dir(self.dist_name)
+ def prepare_dist(self):
+ self.bootstrap.prepare_dist_dir()
- def get_site_packages_dir(self, arch=None):
+ def get_site_packages_dir(self, arch):
'''Returns the location of site-packages in the python-install build
dir.
'''
- if self.python_recipe.name == 'python2legacy':
- return join(self.get_python_install_dir(),
- 'lib', 'python2.7', 'site-packages')
- return self.get_python_install_dir()
+ return self.get_python_install_dir(arch.arch)
def get_libs_dir(self, arch):
'''The libs dir for a given arch.'''
@@ -488,21 +438,10 @@ class Context(object):
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.
+ try:
+ name = get_package_name(os.path.abspath(name))
+ except ValueError:
+ # Failed to look up any meaningful name.
return False
# Try to look up recipe by name:
@@ -517,7 +456,6 @@ class Context(object):
return (exists(join(site_packages_dir, name)) or
exists(join(site_packages_dir, name + '.py')) or
exists(join(site_packages_dir, name + '.pyc')) or
- exists(join(site_packages_dir, name + '.pyo')) or
exists(join(site_packages_dir, name + '.so')) or
glob.glob(join(site_packages_dir, name + '-*.egg')))
@@ -525,7 +463,9 @@ class Context(object):
return not self.has_package(name, arch)
-def build_recipes(build_order, python_modules, ctx):
+def build_recipes(build_order, python_modules, ctx, project_dir,
+ ignore_project_setup_py=False
+ ):
# Put recipes in correct build order
info_notify("Recipe build order is {}".format(build_order))
if python_modules:
@@ -565,13 +505,17 @@ def build_recipes(build_order, python_modules, ctx):
else:
info('{} said it is already built, skipping'
.format(recipe.name))
+ recipe.install_libraries(arch)
# 4) biglink everything
info_main('# Biglinking object files')
- if not ctx.python_recipe or not ctx.python_recipe.from_crystax:
+ if not ctx.python_recipe:
biglink(ctx, arch)
else:
- info('NDK is crystax, skipping biglink (will this work?)')
+ warning(
+ "Context's python recipe found, "
+ "skipping biglink (will this work?)"
+ )
# 5) postbuild packages
info_main('# Postbuilding recipes')
@@ -580,46 +524,185 @@ def build_recipes(build_order, python_modules, ctx):
recipe.postbuild_arch(arch)
info_main('# Installing pure Python modules')
- run_pymodules_install(ctx, python_modules)
-
- return
+ for arch in ctx.archs:
+ run_pymodules_install(
+ ctx, arch, python_modules, project_dir,
+ ignore_setup_py=ignore_project_setup_py
+ )
-def run_pymodules_install(ctx, modules):
- modules = list(filter(ctx.not_has_package, modules))
+def project_has_setup_py(project_dir):
+ return (project_dir is not None and
+ (exists(join(project_dir, "setup.py")) or
+ exists(join(project_dir, "pyproject.toml"))
+ ))
- if not modules:
- info('There are no Python modules to install, skipping')
+
+def run_setuppy_install(ctx, project_dir, env=None, arch=None):
+ env = env or {}
+
+ with current_directory(project_dir):
+ info('got setup.py or similar, running project install. ' +
+ '(disable this behavior with --ignore-setup-py)')
+
+ # Compute & output the constraints we will use:
+ info('Contents that will be used for constraints.txt:')
+ constraints = subprocess.check_output([
+ join(
+ ctx.build_dir, "venv", "bin", "pip"
+ ),
+ "freeze"
+ ], env=copy.copy(env))
+ with suppress(AttributeError):
+ constraints = constraints.decode("utf-8", "replace")
+ info(constraints)
+
+ # Make sure all packages found are fixed in version
+ # by writing a constraint file, to avoid recipes being
+ # upgraded & reinstalled:
+ with open('._tmp_p4a_recipe_constraints.txt', 'wb') as fileh:
+ fileh.write(constraints.encode("utf-8", "replace"))
+ try:
+
+ info('Populating venv\'s site-packages with '
+ 'ctx.get_site_packages_dir()...')
+
+ # Copy dist contents into site-packages for discovery.
+ # Why this is needed:
+ # --target is somewhat evil and messes with discovery of
+ # packages in PYTHONPATH if that also includes the target
+ # folder. So we need to use the regular virtualenv
+ # site-packages folder instead.
+ # Reference:
+ # https://github.com/pypa/pip/issues/6223
+ ctx_site_packages_dir = os.path.normpath(
+ os.path.abspath(ctx.get_site_packages_dir(arch))
+ )
+ venv_site_packages_dir = os.path.normpath(os.path.join(
+ ctx.build_dir, "venv", "lib", [
+ f for f in os.listdir(os.path.join(
+ ctx.build_dir, "venv", "lib"
+ )) if f.startswith("python")
+ ][0], "site-packages"
+ ))
+ copied_over_contents = []
+ for f in os.listdir(ctx_site_packages_dir):
+ full_path = os.path.join(ctx_site_packages_dir, f)
+ if not os.path.exists(os.path.join(
+ venv_site_packages_dir, f
+ )):
+ if os.path.isdir(full_path):
+ shutil.copytree(full_path, os.path.join(
+ venv_site_packages_dir, f
+ ))
+ else:
+ shutil.copy2(full_path, os.path.join(
+ venv_site_packages_dir, f
+ ))
+ copied_over_contents.append(f)
+
+ # Get listing of virtualenv's site-packages, to see the
+ # newly added things afterwards & copy them back into
+ # the distribution folder / build context site-packages:
+ previous_venv_contents = os.listdir(
+ venv_site_packages_dir
+ )
+
+ # Actually run setup.py:
+ info('Launching package install...')
+ shprint(sh.bash, '-c', (
+ "'" + join(
+ ctx.build_dir, "venv", "bin", "pip"
+ ).replace("'", "'\"'\"'") + "' " +
+ "install -c ._tmp_p4a_recipe_constraints.txt -v ."
+ ).format(ctx.get_site_packages_dir(arch).
+ replace("'", "'\"'\"'")),
+ _env=copy.copy(env))
+
+ # Go over all new additions and copy them back:
+ info('Copying additions resulting from setup.py back '
+ 'into ctx.get_site_packages_dir()...')
+ new_venv_additions = []
+ for f in (set(os.listdir(venv_site_packages_dir)) -
+ set(previous_venv_contents)):
+ new_venv_additions.append(f)
+ full_path = os.path.join(venv_site_packages_dir, f)
+ if os.path.isdir(full_path):
+ shutil.copytree(full_path, os.path.join(
+ ctx_site_packages_dir, f
+ ))
+ else:
+ shutil.copy2(full_path, os.path.join(
+ ctx_site_packages_dir, f
+ ))
+
+ # Undo all the changes we did to the venv-site packages:
+ info('Reverting additions to '
+ 'virtualenv\'s site-packages...')
+ for f in set(copied_over_contents + new_venv_additions):
+ full_path = os.path.join(venv_site_packages_dir, f)
+ if os.path.isdir(full_path):
+ shutil.rmtree(full_path)
+ else:
+ os.remove(full_path)
+ finally:
+ os.remove("._tmp_p4a_recipe_constraints.txt")
+
+
+def run_pymodules_install(ctx, arch, modules, project_dir=None,
+ ignore_setup_py=False):
+ """ This function will take care of all non-recipe things, by:
+
+ 1. Processing them from --requirements (the modules argument)
+ and installing them
+
+ 2. Installing the user project/app itself via setup.py if
+ ignore_setup_py=True
+
+ """
+
+ info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE FOR ARCH: {} ***'.format(arch))
+
+ modules = [m for m in modules if ctx.not_has_package(m, arch)]
+
+ # We change current working directory later, so this has to be an absolute
+ # path or `None` in case that we didn't supply the `project_dir` via kwargs
+ project_dir = abspath(project_dir) if project_dir else None
+
+ # Bail out if no python deps and no setup.py to process:
+ if not modules and (
+ ignore_setup_py or
+ project_dir is None or
+ not project_has_setup_py(project_dir)
+ ):
+ info('No Python modules and no setup.py to process, skipping')
return
- info('The requirements ({}) don\'t have recipes, attempting to install '
- 'them with pip'.format(', '.join(modules)))
- info('If this fails, it may mean that the module has compiled '
- 'components and needs a recipe.')
+ # Output messages about what we're going to do:
+ if modules:
+ info(
+ "The requirements ({}) don\'t have recipes, attempting to "
+ "install them with pip".format(', '.join(modules))
+ )
+ info(
+ "If this fails, it may mean that the module has compiled "
+ "components and needs a recipe."
+ )
+ if project_dir is not None and \
+ project_has_setup_py(project_dir) and not ignore_setup_py:
+ info(
+ "Will process project install, if it fails then the "
+ "project may not be compatible for Android install."
+ )
- venv = sh.Command(ctx.virtualenv)
+ # Use our hostpython to create the virtualenv
+ host_python = sh.Command(ctx.hostpython)
with current_directory(join(ctx.build_dir)):
- shprint(venv,
- '--python=python{}.{}'.format(
- ctx.python_recipe.major_minor_version_string.partition(".")[0],
- ctx.python_recipe.major_minor_version_string.partition(".")[2]
- ),
- 'venv'
- )
-
- info('Creating a requirements.txt file for the Python modules')
- with open('requirements.txt', 'w') as fileh:
- for module in modules:
- key = 'VERSION_' + module
- if key in environ:
- line = '{}=={}\n'.format(module, environ[key])
- else:
- line = '{}\n'.format(module)
- fileh.write(line)
+ shprint(host_python, '-m', 'venv', 'venv')
# Prepare base environment and upgrade pip:
- base_env = copy.copy(os.environ)
- base_env["PYTHONPATH"] = ctx.get_site_packages_dir()
+ base_env = dict(copy.copy(os.environ))
+ base_env["PYTHONPATH"] = ctx.get_site_packages_dir(arch)
info('Upgrade pip to latest version')
shprint(sh.bash, '-c', (
"source venv/bin/activate && pip install -U pip"
@@ -628,7 +711,7 @@ def run_pymodules_install(ctx, modules):
# 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"
+ "venv/bin/pip install --upgrade Cython"
), _env=copy.copy(base_env))
# Get environment variables for build (with CC/compiler set):
@@ -640,39 +723,56 @@ def run_pymodules_install(ctx, modules):
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"] += ":" + ctx.get_site_packages_dir(arch)
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))
- '''
+ # Install the manually specified requirements first:
+ if not modules:
+ info('There are no Python modules to install, skipping')
+ else:
+ info('Creating a requirements.txt file for the Python modules')
+ with open('requirements.txt', 'w') as fileh:
+ for module in modules:
+ key = 'VERSION_' + module
+ if key in environ:
+ line = '{}=={}\n'.format(module, environ[key])
+ else:
+ line = '{}\n'.format(module)
+ fileh.write(line)
- # 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()))
+ 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."
+ )
+
+ shprint(sh.bash, '-c', (
+ "venv/bin/pip " +
+ "install -v --target '{0}' --no-deps -r requirements.txt"
+ ).format(ctx.get_site_packages_dir(arch).replace("'", "'\"'\"'")),
+ _env=copy.copy(env))
+
+ # Afterwards, run setup.py if present:
+ if project_dir is not None and (
+ project_has_setup_py(project_dir) and not ignore_setup_py
+ ):
+ run_setuppy_install(ctx, project_dir, env, arch.arch)
+ elif not ignore_setup_py:
+ info("No setup.py found in project directory: " + str(project_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)
+ if not ctx.with_debug_symbols:
+ standard_recipe.strip_object_files(
+ arch, env, build_dir=ctx.build_dir
+ )
def biglink(ctx, arch):
@@ -711,7 +811,7 @@ def biglink(ctx, arch):
# Move to the directory containing crtstart_so.o and crtend_so.o
# This is necessary with newer NDKs? A gcc bug?
- with current_directory(join(ctx.ndk_platform, 'usr', 'lib')):
+ with current_directory(arch.ndk_lib_dir):
do_biglink(
join(ctx.get_libs_dir(arch.arch), 'libpymodules.so'),
obj_dir.split(' '),
@@ -721,7 +821,9 @@ def biglink(ctx, arch):
env=env)
-def biglink_function(soname, objs_paths, extra_link_dirs=[], env=None):
+def biglink_function(soname, objs_paths, extra_link_dirs=None, env=None):
+ if extra_link_dirs is None:
+ extra_link_dirs = []
print('objs_paths are', objs_paths)
sofiles = []
@@ -768,7 +870,9 @@ def biglink_function(soname, objs_paths, extra_link_dirs=[], env=None):
shprint(cc, '-shared', '-O3', '-o', soname, *unique_args, _env=env)
-def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
+def copylibs_function(soname, objs_paths, extra_link_dirs=None, env=None):
+ if extra_link_dirs is None:
+ extra_link_dirs = []
print('objs_paths are', objs_paths)
re_needso = re.compile(r'^.*\(NEEDED\)\s+Shared library: \[lib(.*)\.so\]\s*$')
@@ -800,7 +904,7 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
elif 'READELF' in os.environ:
readelf = os.environ['READELF']
else:
- readelf = sh.which('readelf').strip()
+ readelf = shutil.which('readelf').strip()
readelf = sh.Command(readelf).bake('-d')
dest = dirname(soname)
@@ -896,5 +1000,4 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
'\n\t'.join(needed_libs))
print('Copying libraries')
- for lib in sofiles:
- shprint(sh.cp, lib, dest)
+ shprint(sh.cp, *sofiles, dest)
diff --git a/p4a/pythonforandroid/distribution.py b/p4a/pythonforandroid/distribution.py
index 9fa7b4c..ff97f92 100644
--- a/p4a/pythonforandroid/distribution.py
+++ b/p4a/pythonforandroid/distribution.py
@@ -7,7 +7,7 @@ from pythonforandroid.util import current_directory, BuildInterruptingException
from shutil import rmtree
-class Distribution(object):
+class Distribution:
'''State container for information about a distribution (i.e. an
Android project).
@@ -24,7 +24,7 @@ class Distribution(object):
ndk_api = None
archs = []
- '''The arch targets that the dist is built for.'''
+ '''The names of the arch targets that the dist is built for.'''
recipes = []
@@ -42,12 +42,19 @@ class Distribution(object):
return str(self)
@classmethod
- def get_distribution(cls, ctx, name=None, recipes=[],
- ndk_api=None,
- force_build=False,
- extra_dist_dirs=[],
- require_perfect_match=False,
- allow_replace_dist=True):
+ def get_distribution(
+ cls,
+ ctx,
+ *,
+ archs, # required keyword argument: there is no sensible default
+ name=None,
+ recipes=[],
+ ndk_api=None,
+ force_build=False,
+ extra_dist_dirs=[],
+ require_perfect_match=False,
+ allow_replace_dist=True
+ ):
'''Takes information about the distribution, and decides what kind of
distribution it will be.
@@ -60,6 +67,12 @@ class Distribution(object):
name : str
The name of the distribution. If a dist with this name already '
exists, it will be used.
+ ndk_api : int
+ The NDK API to compile against, included in the dist because it cannot
+ be changed later during APK packaging.
+ archs : list
+ The target architectures list to compile against, included in the dist because
+ it cannot be changed later during APK packaging.
recipes : list
The recipes that the distribution must contain.
force_download: bool
@@ -77,17 +90,24 @@ class Distribution(object):
a new one with the current requirements.
'''
- existing_dists = Distribution.get_distributions(ctx)
+ possible_dists = Distribution.get_distributions(ctx)
- possible_dists = existing_dists
+ # Will hold dists that would be built in the same folder as an existing dist
+ folder_match_dist = None
- name_match_dist = None
-
- # 0) Check if a dist with that name already exists
+ # 0) Check if a dist with that name and architecture already exists
if name is not None and name:
- possible_dists = [d for d in possible_dists if d.name == name]
+ possible_dists = [
+ d for d in possible_dists if
+ (d.name == name) and all(arch_name in d.archs for arch_name in archs)]
+
if possible_dists:
- name_match_dist = possible_dists[0]
+ # There should only be one folder with a given dist name *and* arch.
+ # We could check that here, but for compatibility let's let it slide
+ # and just record the details of one of them. We only use this data to
+ # possibly fail the build later, so it doesn't really matter if there
+ # was more than one clash.
+ folder_match_dist = possible_dists[0]
# 1) Check if any existing dists meet the requirements
_possible_dists = []
@@ -110,12 +130,14 @@ class Distribution(object):
else:
info('No existing dists meet the given requirements!')
- # If any dist has perfect recipes and ndk API, return it
+ # If any dist has perfect recipes, arch 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 not all(arch_name in dist.archs for arch_name in archs):
+ continue
if (set(dist.recipes) == set(recipes) or
(set(recipes).issubset(set(dist.recipes)) and
not require_perfect_match)):
@@ -123,12 +145,10 @@ class Distribution(object):
.format(dist.name))
return dist
- assert len(possible_dists) < 2
-
# 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:
+ if folder_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 '
@@ -136,9 +156,11 @@ class Distribution(object):
'({dist_recipes}) or NDK API {dist_ndk_api}'.format(
name=name,
req_ndk_api=ndk_api,
- dist_ndk_api=name_match_dist.ndk_api,
+ dist_ndk_api=folder_match_dist.ndk_api,
req_recipes=', '.join(recipes),
- dist_recipes=', '.join(name_match_dist.recipes)))
+ dist_recipes=', '.join(folder_match_dist.recipes)))
+
+ assert len(possible_dists) < 2
# If we got this far, we need to build a new dist
dist = Distribution(ctx)
@@ -152,9 +174,12 @@ class Distribution(object):
name = filen.format(i)
dist.name = name
- dist.dist_dir = join(ctx.dist_dir, dist.name)
+ dist.dist_dir = join(
+ ctx.dist_dir,
+ name)
dist.recipes = recipes
dist.ndk_api = ctx.ndk_api
+ dist.archs = archs
return dist
@@ -182,7 +207,7 @@ class Distribution(object):
with open(join(folder, 'dist_info.json')) as fileh:
dist_info = json.load(fileh)
dist = cls(ctx)
- dist.name = folder.split('/')[-1]
+ dist.name = dist_info['dist_name']
dist.dist_dir = folder
dist.needs_build = False
dist.recipes = dist_info['recipes']
@@ -210,10 +235,11 @@ class Distribution(object):
with current_directory(dirn):
info('Saving distribution info')
with open('dist_info.json', 'w') as fileh:
- json.dump({'dist_name': self.ctx.dist_name,
+ json.dump({'dist_name': self.name,
'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
'ndk_api': self.ctx.ndk_api,
+ 'use_setup_py': self.ctx.use_setup_py,
'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
'hostpython': self.ctx.hostpython,
'python_version': self.ctx.python_recipe.major_minor_version_string},
diff --git a/p4a/pythonforandroid/entrypoints.py b/p4a/pythonforandroid/entrypoints.py
new file mode 100644
index 0000000..1ba6a26
--- /dev/null
+++ b/p4a/pythonforandroid/entrypoints.py
@@ -0,0 +1,20 @@
+from pythonforandroid.recommendations import check_python_version
+from pythonforandroid.util import BuildInterruptingException, handle_build_exception
+
+
+def main():
+ """
+ Main entrypoint for running python-for-android as a script.
+ """
+
+ try:
+ # Check the Python version before importing anything heavier than
+ # the util functions. This lets us provide a nice message about
+ # incompatibility rather than having the interpreter crash if it
+ # reaches unsupported syntax from a newer Python version.
+ check_python_version()
+
+ from pythonforandroid.toolchain import ToolchainCL
+ ToolchainCL()
+ except BuildInterruptingException as exc:
+ handle_build_exception(exc)
diff --git a/p4a/pythonforandroid/graph.py b/p4a/pythonforandroid/graph.py
index 646a66e..bdaca43 100644
--- a/p4a/pythonforandroid/graph.py
+++ b/p4a/pythonforandroid/graph.py
@@ -45,7 +45,7 @@ def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
"""
if blacklist is None:
blacklist = set()
- assert(type(blacklist) == set)
+ assert type(blacklist) == set
if recipe.depends is None:
dependencies = []
else:
@@ -130,7 +130,7 @@ def find_order(graph):
'''
while graph:
# Find all items without a parent
- leftmost = [l for l, s in graph.items() if not s]
+ leftmost = [name for name, dep in graph.items() if not dep]
if not leftmost:
raise ValueError('Dependency cycle detected! %s' % graph)
# If there is more than one, sort them for predictable order
@@ -160,7 +160,7 @@ def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
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)
+ 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
@@ -293,7 +293,8 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
orders.append(list(order))
# prefer python3 and SDL2 if available
- orders.sort(key=lambda order: -('python3' in order) - ('sdl2' in order))
+ orders = sorted(orders,
+ key=lambda order: -('python3' in order) - ('sdl2' in order))
if not orders:
raise BuildInterruptingException(
diff --git a/p4a/pythonforandroid/logger.py b/p4a/pythonforandroid/logger.py
index b25b94c..8bcf85c 100644
--- a/p4a/pythonforandroid/logger.py
+++ b/p4a/pythonforandroid/logger.py
@@ -6,18 +6,7 @@ from sys import stdout, stderr
from math import log10
from collections import defaultdict
from colorama import Style as Colo_Style, Fore as Colo_Fore
-import six
-# This codecs change fixes a bug with log output, but crashes under python3
-if not six.PY3:
- import codecs
- stdout = codecs.getwriter('utf8')(stdout)
- stderr = codecs.getwriter('utf8')(stderr)
-
-if six.PY2:
- unistr = unicode # noqa F821
-else:
- unistr = str
# monkey patch to show full output
sh.ErrorReturnCode.truncate_cap = 999999
@@ -40,7 +29,7 @@ class LevelDifferentiatingFormatter(logging.Formatter):
record.msg = '{}{}[DEBUG]{}{}: '.format(
Err_Style.BRIGHT, Err_Fore.LIGHTBLACK_EX, Err_Fore.RESET,
Err_Style.RESET_ALL) + record.msg
- return super(LevelDifferentiatingFormatter, self).format(record)
+ return super().format(record)
logger = logging.getLogger('p4a')
@@ -59,7 +48,7 @@ warning = logger.warning
error = logger.error
-class colorama_shim(object):
+class colorama_shim:
def __init__(self, real):
self._dict = defaultdict(str)
@@ -112,12 +101,12 @@ def shorten_string(string, max_width):
return string
visible = max_width - 16 - int(log10(string_len))
# expected suffix len "...(and XXXXX more)"
- if not isinstance(string, unistr):
- visstring = unistr(string[:visible], errors='ignore')
+ if not isinstance(string, str):
+ visstring = str(string[:visible], errors='ignore')
else:
visstring = string[:visible]
return u''.join((visstring, u'...(and ',
- unistr(string_len - visible), u' more)'))
+ str(string_len - visible), u' more)'))
def get_console_width():
@@ -159,7 +148,6 @@ def shprint(command, *args, **kwargs):
columns = get_console_width()
command_path = str(command).split('/')
command_string = command_path[-1]
-
string = ' '.join(['{}->{} running'.format(Out_Fore.LIGHTBLACK_EX,
Out_Style.RESET_ALL),
command_string] + list(args))
@@ -211,9 +199,9 @@ def shprint(command, *args, **kwargs):
re_filter_in=None, re_filter_out=None):
lines = out.splitlines()
if re_filter_in is not None:
- lines = [l for l in lines if re_filter_in.search(l)]
+ lines = [line for line in lines if re_filter_in.search(line)]
if re_filter_out is not None:
- lines = [l for l in lines if not re_filter_out.search(l)]
+ lines = [line for line in lines if not re_filter_out.search(line)]
if tail_n == 0 or len(lines) <= tail_n:
info('{}:\n{}\t{}{}'.format(
name, forecolor, '\t\n'.join(lines), Out_Fore.RESET))
@@ -226,17 +214,18 @@ def shprint(command, *args, **kwargs):
re.compile(filter_in) if filter_in else None,
re.compile(filter_out) if filter_out else None)
printtail(err.stderr.decode('utf-8'), 'STDERR', Err_Fore.RED)
- if is_critical:
- env = kwargs.get("env")
+ if is_critical or full_debug:
+ env = kwargs.get("_env")
if env is not None:
info("{}ENV:{}\n{}\n".format(
Err_Fore.YELLOW, Err_Fore.RESET, "\n".join(
- "set {}={}".format(n, v) for n, v in env.items())))
+ "export {}='{}'".format(n, v) for n, v in env.items())))
info("{}COMMAND:{}\ncd {} && {} {}\n".format(
Err_Fore.YELLOW, Err_Fore.RESET, os.getcwd(), command,
' '.join(args)))
warning("{}ERROR: {} failed!{}".format(
Err_Fore.RED, command, Err_Fore.RESET))
+ if is_critical:
exit(1)
else:
raise
diff --git a/p4a/pythonforandroid/patching.py b/p4a/pythonforandroid/patching.py
index 2a47733..96a3b27 100644
--- a/p4a/pythonforandroid/patching.py
+++ b/p4a/pythonforandroid/patching.py
@@ -1,4 +1,5 @@
from os import uname
+from distutils.version import LooseVersion
def check_all(*callables):
@@ -69,3 +70,20 @@ def is_ndk(ndk):
def is_x(recipe, **kwargs):
return recipe.ctx.ndk == ndk
return is_x
+
+
+def is_version_gt(version):
+ def is_x(recipe, **kwargs):
+ return LooseVersion(recipe.version) > version
+
+
+def is_version_lt(version):
+ def is_x(recipe, **kwargs):
+ return LooseVersion(recipe.version) < version
+ return is_x
+
+
+def version_starts_with(version):
+ def is_x(recipe, **kwargs):
+ return recipe.version.startswith(version)
+ return is_x
diff --git a/p4a/pythonforandroid/prerequisites.py b/p4a/pythonforandroid/prerequisites.py
new file mode 100644
index 0000000..d85eb0b
--- /dev/null
+++ b/p4a/pythonforandroid/prerequisites.py
@@ -0,0 +1,415 @@
+#!/usr/bin/env python3
+
+import sys
+import platform
+import os
+import subprocess
+import shutil
+from pythonforandroid.logger import info, warning, error
+
+
+class Prerequisite(object):
+ name = "Default"
+ homebrew_formula_name = ""
+ mandatory = dict(linux=False, darwin=False)
+ installer_is_supported = dict(linux=False, darwin=False)
+
+ def is_valid(self):
+ if self.checker():
+ info(f"Prerequisite {self.name} is met")
+ return (True, "")
+ elif not self.mandatory[sys.platform]:
+ warning(
+ f"Prerequisite {self.name} is not met, but is marked as non-mandatory"
+ )
+ else:
+ error(f"Prerequisite {self.name} is not met")
+
+ def checker(self):
+ if sys.platform == "darwin":
+ return self.darwin_checker()
+ elif sys.platform == "linux":
+ return self.linux_checker()
+ else:
+ raise Exception("Unsupported platform")
+
+ def ask_to_install(self):
+ if (
+ os.environ.get("PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE", "1")
+ == "1"
+ ):
+ res = input(
+ f"Do you want automatically install prerequisite {self.name}? [y/N] "
+ )
+ if res.lower() == "y":
+ return True
+ else:
+ return False
+ else:
+ info(
+ "Session is not interactive (usually this happens during a CI run), so let's consider it as a YES"
+ )
+ return True
+
+ def install(self):
+ info(f"python-for-android can automatically install prerequisite: {self.name}")
+ if self.ask_to_install():
+ if sys.platform == "darwin":
+ self.darwin_installer()
+ elif sys.platform == "linux":
+ self.linux_installer()
+ else:
+ raise Exception("Unsupported platform")
+ else:
+ info(
+ f"Skipping installation of prerequisite {self.name} as per user request"
+ )
+
+ def show_helper(self):
+ if sys.platform == "darwin":
+ self.darwin_helper()
+ elif sys.platform == "linux":
+ self.linux_helper()
+ else:
+ raise Exception("Unsupported platform")
+
+ def install_is_supported(self):
+ return self.installer_is_supported[sys.platform]
+
+ def linux_checker(self):
+ raise Exception(f"Unsupported prerequisite check on linux for {self.name}")
+
+ def darwin_checker(self):
+ raise Exception(f"Unsupported prerequisite check on macOS for {self.name}")
+
+ def linux_installer(self):
+ raise Exception(f"Unsupported prerequisite installer on linux for {self.name}")
+
+ def darwin_installer(self):
+ raise Exception(f"Unsupported prerequisite installer on macOS for {self.name}")
+
+ def darwin_helper(self):
+ info(f"No helper available for prerequisite: {self.name} on macOS")
+
+ def linux_helper(self):
+ info(f"No helper available for prerequisite: {self.name} on linux")
+
+ def _darwin_get_brew_formula_location_prefix(self, formula, installed=False):
+ opts = ["--installed"] if installed else []
+ p = subprocess.Popen(
+ ["brew", "--prefix", formula, *opts],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ _stdout_res, _stderr_res = p.communicate()
+
+ if p.returncode != 0:
+ error(_stderr_res.decode("utf-8").strip())
+ return None
+ else:
+ return _stdout_res.decode("utf-8").strip()
+
+ def darwin_pkg_config_location(self):
+ warning(
+ f"pkg-config location is not supported on macOS for prerequisite: {self.name}"
+ )
+ return ""
+
+ def linux_pkg_config_location(self):
+ warning(
+ f"pkg-config location is not supported on linux for prerequisite: {self.name}"
+ )
+ return ""
+
+ @property
+ def pkg_config_location(self):
+ if sys.platform == "darwin":
+ return self.darwin_pkg_config_location()
+ elif sys.platform == "linux":
+ return self.linux_pkg_config_location()
+
+
+class HomebrewPrerequisite(Prerequisite):
+ name = "homebrew"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=False)
+
+ def darwin_checker(self):
+ return shutil.which("brew") is not None
+
+ def darwin_helper(self):
+ info(
+ "Installer for homebrew is not yet supported on macOS,"
+ "the nice news is that the installation process is easy!"
+ "See: https://brew.sh for further instructions."
+ )
+
+
+class JDKPrerequisite(Prerequisite):
+ name = "JDK"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+ min_supported_version = 11
+
+ def darwin_checker(self):
+ if "JAVA_HOME" in os.environ:
+ info("Found JAVA_HOME environment variable, using it")
+ jdk_path = os.environ["JAVA_HOME"]
+ else:
+ jdk_path = self._darwin_get_libexec_jdk_path(version=None)
+ return self._darwin_jdk_is_supported(jdk_path)
+
+ def _darwin_get_libexec_jdk_path(self, version=None):
+ version_args = []
+ if version is not None:
+ version_args = ["-v", version]
+ return (
+ subprocess.run(
+ ["/usr/libexec/java_home", *version_args],
+ stdout=subprocess.PIPE,
+ )
+ .stdout.strip()
+ .decode()
+ )
+
+ def _darwin_jdk_is_supported(self, jdk_path):
+ if not jdk_path:
+ return False
+
+ javac_bin = os.path.join(jdk_path, "bin", "javac")
+ if not os.path.exists(javac_bin):
+ return False
+
+ p = subprocess.Popen(
+ [javac_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ _stdout_res, _stderr_res = p.communicate()
+
+ if p.returncode != 0:
+ error("Failed to run javac to check JDK version")
+ return False
+
+ if not _stdout_res:
+ _stdout_res = _stderr_res
+
+ res = _stdout_res.strip().decode()
+
+ major_version = int(res.split(" ")[-1].split(".")[0])
+ if major_version >= self.min_supported_version:
+ info(f"Found a valid JDK at {jdk_path}")
+ return True
+ else:
+ error(f"JDK {self.min_supported_version} or higher is required")
+ return False
+
+ def darwin_helper(self):
+ info(
+ "python-for-android requires a JDK 11 or higher to be installed on macOS,"
+ "but seems like you don't have one installed."
+ )
+ info(
+ "If you think that a valid JDK is already installed, please verify that "
+ "you have a JDK 11 or higher installed and that `/usr/libexec/java_home` "
+ "shows the correct path."
+ )
+ info(
+ "If you have multiple JDK installations, please make sure that you have "
+ "`JAVA_HOME` environment variable set to the correct JDK installation."
+ )
+
+ def darwin_installer(self):
+ info(
+ "Looking for a JDK 11 or higher installation which is not the default one ..."
+ )
+ jdk_path = self._darwin_get_libexec_jdk_path(version="11+")
+
+ if not self._darwin_jdk_is_supported(jdk_path):
+ info("We're unlucky, there's no JDK 11 or higher installation available")
+
+ base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
+ if platform.machine() == "arm64":
+ filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
+ else:
+ filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
+
+ info(f"Downloading {filename} from {base_url}")
+ subprocess.check_output(
+ [
+ "curl",
+ "-L",
+ f"{base_url}{filename}",
+ "-o",
+ f"/tmp/{filename}",
+ ]
+ )
+
+ user_library_java_path = os.path.expanduser(
+ "~/Library/Java/JavaVirtualMachines"
+ )
+ info(f"Extracting {filename} to {user_library_java_path}")
+ subprocess.check_output(
+ [
+ "mkdir",
+ "-p",
+ user_library_java_path,
+ ],
+ )
+ subprocess.check_output(
+ ["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path],
+ )
+
+ jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
+
+ info(f"Setting JAVA_HOME to {jdk_path}")
+ os.environ["JAVA_HOME"] = jdk_path
+
+
+class OpenSSLPrerequisite(Prerequisite):
+ name = "openssl"
+ homebrew_formula_name = "openssl@1.1"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix(
+ self.homebrew_formula_name, installed=True
+ )
+ is not None
+ )
+
+ def darwin_pkg_config_location(self):
+ return os.path.join(
+ self._darwin_get_brew_formula_location_prefix(self.homebrew_formula_name),
+ "lib/pkgconfig",
+ )
+
+ def darwin_installer(self):
+ info("Installing OpenSSL ...")
+ subprocess.check_output(["brew", "install", self.homebrew_formula_name])
+
+
+class AutoconfPrerequisite(Prerequisite):
+ name = "autoconf"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("autoconf", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Autoconf ...")
+ subprocess.check_output(["brew", "install", "autoconf"])
+
+
+class AutomakePrerequisite(Prerequisite):
+ name = "automake"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("automake", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Automake ...")
+ subprocess.check_output(["brew", "install", "automake"])
+
+
+class LibtoolPrerequisite(Prerequisite):
+ name = "libtool"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("libtool", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Libtool ...")
+ subprocess.check_output(["brew", "install", "libtool"])
+
+
+class PkgConfigPrerequisite(Prerequisite):
+ name = "pkg-config"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("pkg-config", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Pkg-Config ...")
+ subprocess.check_output(["brew", "install", "pkg-config"])
+
+
+class CmakePrerequisite(Prerequisite):
+ name = "cmake"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("cmake", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing cmake ...")
+ subprocess.check_output(["brew", "install", "cmake"])
+
+
+def get_required_prerequisites(platform="linux"):
+ DEFAULT_PREREQUISITES = dict(
+ darwin=[
+ HomebrewPrerequisite(),
+ AutoconfPrerequisite(),
+ AutomakePrerequisite(),
+ LibtoolPrerequisite(),
+ PkgConfigPrerequisite(),
+ CmakePrerequisite(),
+ OpenSSLPrerequisite(),
+ JDKPrerequisite(),
+ ],
+ linux=[],
+ all_platforms=[],
+ )
+
+ return DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[platform]
+
+
+def check_and_install_default_prerequisites():
+
+ prerequisites_not_met = []
+
+ warning(
+ "prerequisites.py is experimental and does not support all prerequisites yet."
+ )
+ warning("Please report any issues to the python-for-android issue tracker.")
+
+ # Phase 1: Check if all prerequisites are met and add the ones
+ # which are not to `prerequisites_not_met`
+ for prerequisite in get_required_prerequisites(sys.platform):
+ if not prerequisite.is_valid():
+ prerequisites_not_met.append(prerequisite)
+
+ # Phase 2: Setup/Install all prerequisites that are not met
+ # (where possible), otherwise show an helper.
+ for prerequisite in prerequisites_not_met:
+ prerequisite.show_helper()
+ if prerequisite.install_is_supported():
+ prerequisite.install()
+
+
+if __name__ == "__main__":
+ check_and_install_default_prerequisites()
diff --git a/p4a/pythonforandroid/python.py b/p4a/pythonforandroid/python.py
deleted file mode 100755
index 3a214ee..0000000
--- a/p4a/pythonforandroid/python.py
+++ /dev/null
@@ -1,437 +0,0 @@
-'''
-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/pythonpackage.py b/p4a/pythonforandroid/pythonpackage.py
new file mode 100644
index 0000000..3b03a51
--- /dev/null
+++ b/p4a/pythonforandroid/pythonpackage.py
@@ -0,0 +1,787 @@
+""" This module offers highlevel functions to get package metadata
+ like the METADATA file, the name, or a list of dependencies.
+
+ Usage examples:
+
+ # Getting package name from pip reference:
+ from pytonforandroid.pythonpackage import get_package_name
+ print(get_package_name("pillow"))
+ # Outputs: "Pillow" (note the spelling!)
+
+ # Getting package dependencies:
+ from pytonforandroid.pythonpackage import get_package_dependencies
+ print(get_package_dependencies("pep517"))
+ # Outputs: "['pytoml']"
+
+ # Get package name from arbitrary package source:
+ from pytonforandroid.pythonpackage import get_package_name
+ print(get_package_name("/some/local/project/folder/"))
+ # Outputs package name
+
+ NOTE:
+
+ Yes, this module doesn't fit well into python-for-android, but this
+ functionality isn't available ANYWHERE ELSE, and upstream (pip, ...)
+ currently has no interest in taking this over, so it has no other place
+ to go.
+ (Unless someone reading this puts it into yet another packaging lib)
+
+ Reference discussion/upstream inclusion attempt:
+
+ https://github.com/pypa/packaging-problems/issues/247
+
+"""
+
+
+import functools
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+import textwrap
+import time
+import zipfile
+from io import open # needed for python 2
+from urllib.parse import unquote as urlunquote
+from urllib.parse import urlparse
+
+import toml
+from pep517.envbuild import BuildEnvironment
+from pep517.wrappers import Pep517HookCaller
+
+
+def transform_dep_for_pip(dependency):
+ if dependency.find("@") > 0 and (
+ dependency.find("@") < dependency.find("://") or
+ "://" not in dependency
+ ):
+ # WORKAROUND FOR UPSTREAM BUG:
+ # https://github.com/pypa/pip/issues/6097
+ # (Please REMOVE workaround once that is fixed & released upstream!)
+ #
+ # Basically, setup_requires() can contain a format pip won't install
+ # from a requirements.txt (PEP 508 URLs).
+ # To avoid this, translate to an #egg= reference:
+ if dependency.endswith("#"):
+ dependency = dependency[:-1]
+ url = (dependency.partition("@")[2].strip().partition("#egg")[0] +
+ "#egg=" +
+ dependency.partition("@")[0].strip()
+ )
+ return url
+ return dependency
+
+
+def extract_metainfo_files_from_package(
+ package,
+ output_folder,
+ debug=False
+ ):
+ """ Extracts metdata files from the given package to the given folder,
+ which may be referenced in any way that is permitted in
+ a requirements.txt file or install_requires=[] listing.
+
+ Current supported metadata files that will be extracted:
+
+ - pytoml.yml (only if package wasn't obtained as wheel)
+ - METADATA
+ """
+
+ if package is None:
+ raise ValueError("package cannot be None")
+
+ if not os.path.exists(output_folder) or os.path.isfile(output_folder):
+ raise ValueError("output folder needs to be existing folder")
+
+ if debug:
+ print("extract_metainfo_files_from_package: extracting for " +
+ "package: " + str(package))
+
+ # A temp folder for making a package copy in case it's a local folder,
+ # because extracting metadata might modify files
+ # (creating sdists/wheels...)
+ temp_folder = tempfile.mkdtemp(prefix="pythonpackage-package-copy-")
+ try:
+ # Package is indeed a folder! Get a temp copy to work on:
+ if is_filesystem_path(package):
+ shutil.copytree(
+ parse_as_folder_reference(package),
+ os.path.join(temp_folder, "package"),
+ ignore=shutil.ignore_patterns(".tox")
+ )
+ package = os.path.join(temp_folder, "package")
+
+ # Because PEP517 can be noisy and contextlib.redirect_* fails to
+ # contain it, we will run the actual analysis in a separate process:
+ try:
+ subprocess.check_output([
+ sys.executable,
+ "-c",
+ "import importlib\n"
+ "import json\n"
+ "import os\n"
+ "import sys\n"
+ "sys.path = [os.path.dirname(sys.argv[3])] + sys.path\n"
+ "m = importlib.import_module(\n"
+ " os.path.basename(sys.argv[3]).partition('.')[0]\n"
+ ")\n"
+ "m._extract_metainfo_files_from_package_unsafe("
+ " sys.argv[1],"
+ " sys.argv[2],"
+ ")",
+ package, output_folder, os.path.abspath(__file__)],
+ stderr=subprocess.STDOUT, # make sure stderr is muted.
+ cwd=os.path.join(os.path.dirname(__file__), "..")
+ )
+ except subprocess.CalledProcessError as e:
+ output = e.output.decode("utf-8", "replace")
+ if debug:
+ print("Got error obtaining meta info.")
+ print("Detail output:")
+ print(output)
+ print("End of Detail output.")
+ raise ValueError(
+ "failed to obtain meta info - "
+ "is '{}' a valid package? "
+ "Detailed output:\n{}".format(package, output)
+ )
+ finally:
+ shutil.rmtree(temp_folder)
+
+
+def _get_system_python_executable():
+ """ Returns the path the system-wide python binary.
+ (In case we're running in a virtualenv or venv)
+ """
+ # This function is required by get_package_as_folder() to work
+ # inside a virtualenv, since venv creation will fail with
+ # the virtualenv's local python binary.
+ # (venv/virtualenv incompatibility)
+
+ # Abort if not in virtualenv or venv:
+ if not hasattr(sys, "real_prefix") and (
+ not hasattr(sys, "base_prefix") or
+ os.path.normpath(sys.base_prefix) ==
+ os.path.normpath(sys.prefix)):
+ return sys.executable
+
+ # Extract prefix we need to look in:
+ if hasattr(sys, "real_prefix"):
+ search_prefix = sys.real_prefix # virtualenv
+ else:
+ search_prefix = sys.base_prefix # venv
+
+ def python_binary_from_folder(path):
+ def binary_is_usable(python_bin):
+ """ Helper function to see if a given binary name refers
+ to a usable python interpreter binary
+ """
+
+ # Abort if path isn't present at all or a directory:
+ if not os.path.exists(
+ os.path.join(path, python_bin)
+ ) or os.path.isdir(os.path.join(path, python_bin)):
+ return
+ # We should check file not found anyway trying to run it,
+ # since it might be a dead symlink:
+ try:
+ filenotfounderror = FileNotFoundError
+ except NameError: # Python 2
+ filenotfounderror = OSError
+ try:
+ # Run it and see if version output works with no error:
+ subprocess.check_output([
+ os.path.join(path, python_bin), "--version"
+ ], stderr=subprocess.STDOUT)
+ return True
+ except (subprocess.CalledProcessError, filenotfounderror):
+ return False
+
+ python_name = "python" + sys.version
+ while (not binary_is_usable(python_name) and
+ python_name.find(".") > 0):
+ # Try less specific binary name:
+ python_name = python_name.rpartition(".")[0]
+ if binary_is_usable(python_name):
+ return os.path.join(path, python_name)
+ return None
+
+ # Return from sys.real_prefix if present:
+ result = python_binary_from_folder(search_prefix)
+ if result is not None:
+ return result
+
+ # Check out all paths in $PATH:
+ bad_candidates = []
+ good_candidates = []
+ ever_had_nonvenv_path = False
+ ever_had_path_starting_with_prefix = False
+ for p in os.environ.get("PATH", "").split(":"):
+ # Skip if not possibly the real system python:
+ if not os.path.normpath(p).startswith(
+ os.path.normpath(search_prefix)
+ ):
+ continue
+
+ ever_had_path_starting_with_prefix = True
+
+ # First folders might be virtualenv/venv we want to avoid:
+ if not ever_had_nonvenv_path:
+ sep = os.path.sep
+ if (
+ ("system32" not in p.lower() and
+ "usr" not in p and
+ not p.startswith("/opt/python")) or
+ {"home", ".tox"}.intersection(set(p.split(sep))) or
+ "users" in p.lower()
+ ):
+ # Doesn't look like bog-standard system path.
+ if (p.endswith(os.path.sep + "bin") or
+ p.endswith(os.path.sep + "bin" + os.path.sep)):
+ # Also ends in "bin" -> likely virtualenv/venv.
+ # Add as unfavorable / end of candidates:
+ bad_candidates.append(p)
+ continue
+ ever_had_nonvenv_path = True
+
+ good_candidates.append(p)
+
+ # If we have a bad env with PATH not containing any reference to our
+ # real python (travis, why would you do that to me?) then just guess
+ # based from the search prefix location itself:
+ if not ever_had_path_starting_with_prefix:
+ # ... and yes we're scanning all the folders for that, it's dumb
+ # but i'm not aware of a better way: (@JonasT)
+ for root, dirs, files in os.walk(search_prefix, topdown=True):
+ for name in dirs:
+ bad_candidates.append(os.path.join(root, name))
+
+ # Sort candidates by length (to prefer shorter ones):
+ def candidate_cmp(a, b):
+ return len(a) - len(b)
+ good_candidates = sorted(
+ good_candidates, key=functools.cmp_to_key(candidate_cmp)
+ )
+ bad_candidates = sorted(
+ bad_candidates, key=functools.cmp_to_key(candidate_cmp)
+ )
+
+ # See if we can now actually find the system python:
+ for p in good_candidates + bad_candidates:
+ result = python_binary_from_folder(p)
+ if result is not None:
+ return result
+
+ raise RuntimeError(
+ "failed to locate system python in: {}"
+ " - checked candidates were: {}, {}"
+ .format(sys.real_prefix, good_candidates, bad_candidates)
+ )
+
+
+def get_package_as_folder(dependency):
+ """ This function downloads the given package / dependency and extracts
+ the raw contents into a folder.
+
+ Afterwards, it returns a tuple with the type of distribution obtained,
+ and the temporary folder it extracted to. It is the caller's
+ responsibility to delete the returned temp folder after use.
+
+ Examples of returned values:
+
+ ("source", "/tmp/pythonpackage-venv-e84toiwjw")
+ ("wheel", "/tmp/pythonpackage-venv-85u78uj")
+
+ What the distribution type will be depends on what pip decides to
+ download.
+ """
+
+ venv_parent = tempfile.mkdtemp(
+ prefix="pythonpackage-venv-"
+ )
+ try:
+ # Create a venv to install into:
+ try:
+ if int(sys.version.partition(".")[0]) < 3:
+ # Python 2.x has no venv.
+ subprocess.check_output([
+ sys.executable, # no venv conflict possible,
+ # -> no need to use system python
+ "-m", "virtualenv",
+ "--python=" + _get_system_python_executable(),
+ os.path.join(venv_parent, 'venv')
+ ], cwd=venv_parent)
+ else:
+ # On modern Python 3, use venv.
+ subprocess.check_output([
+ _get_system_python_executable(), "-m", "venv",
+ os.path.join(venv_parent, 'venv')
+ ], cwd=venv_parent)
+ except subprocess.CalledProcessError as e:
+ output = e.output.decode('utf-8', 'replace')
+ raise ValueError(
+ 'venv creation unexpectedly ' +
+ 'failed. error output: ' + str(output)
+ )
+ venv_path = os.path.join(venv_parent, "venv")
+
+ # Update pip and wheel in venv for latest feature support:
+ try:
+ filenotfounderror = FileNotFoundError
+ except NameError: # Python 2.
+ filenotfounderror = OSError
+ try:
+ subprocess.check_output([
+ os.path.join(venv_path, "bin", "pip"),
+ "install", "-U", "pip", "wheel",
+ ])
+ except filenotfounderror:
+ raise RuntimeError(
+ "venv appears to be missing pip. "
+ "did we fail to use a proper system python??\n"
+ "system python path detected: {}\n"
+ "os.environ['PATH']: {}".format(
+ _get_system_python_executable(),
+ os.environ.get("PATH", "")
+ )
+ )
+
+ # Create download subfolder:
+ os.mkdir(os.path.join(venv_path, "download"))
+
+ # Write a requirements.txt with our package and download:
+ with open(os.path.join(venv_path, "requirements.txt"),
+ "w", encoding="utf-8"
+ ) as f:
+ def to_unicode(s): # Needed for Python 2.
+ try:
+ return s.decode("utf-8")
+ except AttributeError:
+ return s
+ f.write(to_unicode(transform_dep_for_pip(dependency)))
+ try:
+ subprocess.check_output(
+ [
+ os.path.join(venv_path, "bin", "pip"),
+ "download", "--no-deps", "-r", "../requirements.txt",
+ "-d", os.path.join(venv_path, "download")
+ ],
+ stderr=subprocess.STDOUT,
+ cwd=os.path.join(venv_path, "download")
+ )
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError("package download failed: " + str(e.output))
+
+ if len(os.listdir(os.path.join(venv_path, "download"))) == 0:
+ # No download. This can happen if the dependency has a condition
+ # which prohibits install in our environment.
+ # (the "package ; ... conditional ... " type of condition)
+ return (None, None)
+
+ # Get the result and make sure it's an extracted directory:
+ result_folder_or_file = os.path.join(
+ venv_path, "download",
+ os.listdir(os.path.join(venv_path, "download"))[0]
+ )
+ dl_type = "source"
+ if not os.path.isdir(result_folder_or_file):
+ # Must be an archive.
+ if result_folder_or_file.endswith((".zip", ".whl")):
+ if result_folder_or_file.endswith(".whl"):
+ dl_type = "wheel"
+ with zipfile.ZipFile(result_folder_or_file) as f:
+ f.extractall(os.path.join(venv_path,
+ "download", "extracted"
+ ))
+ result_folder_or_file = os.path.join(
+ venv_path, "download", "extracted"
+ )
+ elif result_folder_or_file.find(".tar.") > 0:
+ # Probably a tarball.
+ with tarfile.open(result_folder_or_file) as f:
+ f.extractall(os.path.join(venv_path,
+ "download", "extracted"
+ ))
+ result_folder_or_file = os.path.join(
+ venv_path, "download", "extracted"
+ )
+ else:
+ raise RuntimeError(
+ "unknown archive or download " +
+ "type: " + str(result_folder_or_file)
+ )
+
+ # If the result is hidden away in an additional subfolder,
+ # descend into it:
+ while os.path.isdir(result_folder_or_file) and \
+ len(os.listdir(result_folder_or_file)) == 1 and \
+ os.path.isdir(os.path.join(
+ result_folder_or_file,
+ os.listdir(result_folder_or_file)[0]
+ )):
+ result_folder_or_file = os.path.join(
+ result_folder_or_file,
+ os.listdir(result_folder_or_file)[0]
+ )
+
+ # Copy result to new dedicated folder so we can throw away
+ # our entire virtualenv nonsense after returning:
+ result_path = tempfile.mkdtemp()
+ shutil.rmtree(result_path)
+ shutil.copytree(result_folder_or_file, result_path)
+ return (dl_type, result_path)
+ finally:
+ shutil.rmtree(venv_parent)
+
+
+def _extract_metainfo_files_from_package_unsafe(
+ package,
+ output_path
+ ):
+ # This is the unwrapped function that will
+ # 1. make lots of stdout/stderr noise
+ # 2. possibly modify files (if the package source is a local folder)
+ # Use extract_metainfo_files_from_package_folder instead which avoids
+ # these issues.
+
+ clean_up_path = False
+ path_type = "source"
+ path = parse_as_folder_reference(package)
+ if path is None:
+ # This is not a path. Download it:
+ (path_type, path) = get_package_as_folder(package)
+ if path_type is None:
+ # Download failed.
+ raise ValueError(
+ "cannot get info for this package, " +
+ "pip says it has no downloads (conditional dependency?)"
+ )
+ clean_up_path = True
+
+ try:
+ build_requires = []
+ metadata_path = None
+
+ if path_type != "wheel":
+ # We need to process this first to get the metadata.
+
+ # Ensure pyproject.toml is available (pep517 expects it)
+ if not os.path.exists(os.path.join(path, "pyproject.toml")):
+ with open(os.path.join(path, "pyproject.toml"), "w") as f:
+ f.write(textwrap.dedent(u"""\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+ """))
+
+ # Copy the pyproject.toml:
+ shutil.copyfile(
+ os.path.join(path, 'pyproject.toml'),
+ os.path.join(output_path, 'pyproject.toml')
+ )
+
+ # Get build backend and requirements from pyproject.toml:
+ with open(os.path.join(path, 'pyproject.toml')) as f:
+ build_sys = toml.load(f)['build-system']
+ backend = build_sys["build-backend"]
+ build_requires.extend(build_sys["requires"])
+
+ # Get a virtualenv with build requirements and get all metadata:
+ env = BuildEnvironment()
+ metadata = None
+ with env:
+ hooks = Pep517HookCaller(path, backend)
+ env.pip_install(
+ [transform_dep_for_pip(req) for req in build_requires]
+ )
+ reqs = hooks.get_requires_for_build_wheel({})
+ env.pip_install([transform_dep_for_pip(req) for req in reqs])
+ try:
+ metadata = hooks.prepare_metadata_for_build_wheel(path)
+ except Exception: # sadly, pep517 has no good error here
+ pass
+ if metadata is not None:
+ metadata_path = os.path.join(
+ path, metadata, "METADATA"
+ )
+ else:
+ # This is a wheel, so metadata should be in *.dist-info folder:
+ metadata_path = os.path.join(
+ path,
+ [f for f in os.listdir(path) if f.endswith(".dist-info")][0],
+ "METADATA"
+ )
+
+ # Store type of metadata source. Can be "wheel", "source" for source
+ # distribution, and others get_package_as_folder() may support
+ # in the future.
+ with open(os.path.join(output_path, "metadata_source"), "w") as f:
+ try:
+ f.write(path_type)
+ except TypeError: # in python 2 path_type may be str/bytes:
+ f.write(path_type.decode("utf-8", "replace"))
+
+ # Copy the metadata file:
+ shutil.copyfile(metadata_path, os.path.join(output_path, "METADATA"))
+ finally:
+ if clean_up_path:
+ shutil.rmtree(path)
+
+
+def is_filesystem_path(dep):
+ """ Convenience function around parse_as_folder_reference() to
+ check if a dependency refers to a folder path or something remote.
+
+ Returns True if local, False if remote.
+ """
+ return (parse_as_folder_reference(dep) is not None)
+
+
+def parse_as_folder_reference(dep):
+ """ See if a dependency reference refers to a folder path.
+ If it does, return the folder path (which parses and
+ resolves file:// urls in the process).
+ If it doesn't, return None.
+ """
+ # Special case: pep508 urls
+ if dep.find("@") > 0 and (
+ (dep.find("@") < dep.find("/") or "/" not in dep) and
+ (dep.find("@") < dep.find(":") or ":" not in dep)
+ ):
+ # This should be a 'pkgname @ https://...' style path, or
+ # 'pkname @ /local/file/path'.
+ return parse_as_folder_reference(dep.partition("@")[2].lstrip())
+
+ # Check if this is either not an url, or a file URL:
+ if dep.startswith(("/", "file://")) or (
+ dep.find("/") > 0 and
+ dep.find("://") < 0) or (dep in ["", "."]):
+ if dep.startswith("file://"):
+ dep = urlunquote(urlparse(dep).path)
+ return dep
+ return None
+
+
+def _extract_info_from_package(dependency,
+ extract_type=None,
+ debug=False,
+ include_build_requirements=False
+ ):
+ """ Internal function to extract metainfo from a package.
+ Currently supported info types:
+
+ - name
+ - dependencies (a list of dependencies)
+ """
+ if debug:
+ print("_extract_info_from_package called with "
+ "extract_type={} include_build_requirements={}".format(
+ extract_type, include_build_requirements,
+ ))
+ output_folder = tempfile.mkdtemp(prefix="pythonpackage-metafolder-")
+ try:
+ extract_metainfo_files_from_package(
+ dependency, output_folder, debug=debug
+ )
+
+ # Extract the type of data source we used to get the metadata:
+ with open(os.path.join(output_folder,
+ "metadata_source"), "r") as f:
+ metadata_source_type = f.read().strip()
+
+ # Extract main METADATA file:
+ with open(os.path.join(output_folder, "METADATA"),
+ "r", encoding="utf-8"
+ ) as f:
+ # Get metadata and cut away description (is after 2 linebreaks)
+ metadata_entries = f.read().partition("\n\n")[0].splitlines()
+
+ if extract_type == "name":
+ name = None
+ for meta_entry in metadata_entries:
+ if meta_entry.lower().startswith("name:"):
+ return meta_entry.partition(":")[2].strip()
+ if name is None:
+ raise ValueError("failed to obtain package name")
+ return name
+ elif extract_type == "dependencies":
+ # First, make sure we don't attempt to return build requirements
+ # for wheels since they usually come without pyproject.toml
+ # and we haven't implemented another way to get them:
+ if include_build_requirements and \
+ metadata_source_type == "wheel":
+ if debug:
+ print("_extract_info_from_package: was called "
+ "with include_build_requirements=True on "
+ "package obtained as wheel, raising error...")
+ raise NotImplementedError(
+ "fetching build requirements for "
+ "wheels is not implemented"
+ )
+
+ # Get build requirements from pyproject.toml if requested:
+ requirements = []
+ if os.path.exists(os.path.join(output_folder,
+ 'pyproject.toml')
+ ) and include_build_requirements:
+ # Read build system from pyproject.toml file: (PEP518)
+ with open(os.path.join(output_folder, 'pyproject.toml')) as f:
+ build_sys = toml.load(f)['build-system']
+ if "requires" in build_sys:
+ requirements += build_sys["requires"]
+ elif include_build_requirements:
+ # For legacy packages with no pyproject.toml, we have to
+ # add setuptools as default build system.
+ requirements.append("setuptools")
+
+ # Add requirements from metadata:
+ requirements += [
+ entry.rpartition("Requires-Dist:")[2].strip()
+ for entry in metadata_entries
+ if entry.startswith("Requires-Dist")
+ ]
+
+ return list(set(requirements)) # remove duplicates
+ finally:
+ shutil.rmtree(output_folder)
+
+
+package_name_cache = dict()
+
+
+def get_package_name(dependency,
+ use_cache=True):
+ def timestamp():
+ try:
+ return time.monotonic()
+ except AttributeError:
+ return time.time() # Python 2.
+ try:
+ value = package_name_cache[dependency]
+ if value[0] + 600.0 > timestamp() and use_cache:
+ return value[1]
+ except KeyError:
+ pass
+ result = _extract_info_from_package(dependency, extract_type="name")
+ package_name_cache[dependency] = (timestamp(), result)
+ return result
+
+
+def get_package_dependencies(package,
+ recursive=False,
+ verbose=False,
+ include_build_requirements=False):
+ """ Obtain the dependencies from a package. Please note this
+ function is possibly SLOW, especially if you enable
+ the recursive mode.
+ """
+ packages_processed = set()
+ package_queue = [package]
+ reqs = set()
+ reqs_as_names = set()
+ while len(package_queue) > 0:
+ current_queue = package_queue
+ package_queue = []
+ for package_dep in current_queue:
+ new_reqs = set()
+ if verbose:
+ print("get_package_dependencies: resolving dependency "
+ f"to package name: {package_dep}")
+ package = get_package_name(package_dep)
+ if package.lower() in packages_processed:
+ continue
+ if verbose:
+ print("get_package_dependencies: "
+ "processing package: {}".format(package))
+ print("get_package_dependencies: "
+ "Packages seen so far: {}".format(
+ packages_processed
+ ))
+ packages_processed.add(package.lower())
+
+ # Use our regular folder processing to examine:
+ new_reqs = new_reqs.union(_extract_info_from_package(
+ package_dep, extract_type="dependencies",
+ debug=verbose,
+ include_build_requirements=include_build_requirements,
+ ))
+
+ # Process new requirements:
+ if verbose:
+ print('get_package_dependencies: collected '
+ "deps of '{}': {}".format(
+ package_dep, str(new_reqs),
+ ))
+ for new_req in new_reqs:
+ try:
+ req_name = get_package_name(new_req)
+ except ValueError as e:
+ if new_req.find(";") >= 0:
+ # Conditional dep where condition isn't met?
+ # --> ignore it
+ continue
+ if verbose:
+ print("get_package_dependencies: " +
+ "unexpected failure to get name " +
+ "of '" + str(new_req) + "': " +
+ str(e))
+ raise RuntimeError(
+ "failed to get " +
+ "name of dependency: " + str(e)
+ )
+ if req_name.lower() in reqs_as_names:
+ continue
+ if req_name.lower() not in packages_processed:
+ package_queue.append(new_req)
+ reqs.add(new_req)
+ reqs_as_names.add(req_name.lower())
+
+ # Bail out here if we're not scanning recursively:
+ if not recursive:
+ package_queue[:] = [] # wipe queue
+ break
+ if verbose:
+ print("get_package_dependencies: returning result: {}".format(reqs))
+ return reqs
+
+
+def get_dep_names_of_package(
+ package,
+ keep_version_pins=False,
+ recursive=False,
+ verbose=False,
+ include_build_requirements=False
+ ):
+ """ Gets the dependencies from the package in the given folder,
+ then attempts to deduce the actual package name resulting
+ from each dependency line, stripping away everything else.
+ """
+
+ # First, obtain the dependencies:
+ dependencies = get_package_dependencies(
+ package, recursive=recursive, verbose=verbose,
+ include_build_requirements=include_build_requirements,
+ )
+ if verbose:
+ print("get_dep_names_of_package_folder: " +
+ "processing dependency list to names: " +
+ str(dependencies))
+
+ # Transform dependencies to their stripped down names:
+ # (they can still have version pins/restrictions, conditionals, ...)
+ dependency_names = set()
+ for dep in dependencies:
+ # If we are supposed to keep exact version pins, extract first:
+ pin_to_append = ""
+ if keep_version_pins and "(==" in dep and dep.endswith(")"):
+ # This is a dependency of the format: 'pkg (==1.0)'
+ pin_to_append = "==" + dep.rpartition("==")[2][:-1]
+ elif keep_version_pins and "==" in dep and not dep.endswith(")"):
+ # This is a dependency of the format: 'pkg==1.0'
+ pin_to_append = "==" + dep.rpartition("==")[2]
+ # Now get true (and e.g. case-corrected) dependency name:
+ dep_name = get_package_name(dep) + pin_to_append
+ dependency_names.add(dep_name)
+ return dependency_names
diff --git a/p4a/pythonforandroid/recipe.py b/p4a/pythonforandroid/recipe.py
index 071aa22..67c309e 100644
--- a/p4a/pythonforandroid/recipe.py
+++ b/p4a/pythonforandroid/recipe.py
@@ -1,8 +1,6 @@
from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split
-import importlib
import glob
from shutil import rmtree
-from six import PY2, with_metaclass
import hashlib
from re import match
@@ -10,6 +8,8 @@ from re import match
import sh
import shutil
import fnmatch
+import urllib.request
+from urllib.request import urlretrieve
from os import listdir, unlink, environ, mkdir, curdir, walk
from sys import stdout
import time
@@ -18,26 +18,14 @@ try:
except ImportError:
from urllib.parse import urlparse
from pythonforandroid.logger import (logger, info, warning, debug, shprint, info_main)
-from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir,
+from pythonforandroid.util import (current_directory, ensure_dir,
BuildInterruptingException)
+from pythonforandroid.util import load_source as import_recipe
-# this import is necessary to keep imp.load_source from complaining :)
-if PY2:
- import imp
- import_recipe = imp.load_source
-else:
- import importlib.util
- if hasattr(importlib.util, 'module_from_spec'):
- def import_recipe(module, filename):
- spec = importlib.util.spec_from_file_location(module, filename)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- return mod
- else:
- from importlib.machinery import SourceFileLoader
- def import_recipe(module, filename):
- return SourceFileLoader(module, filename).load_module()
+url_opener = urllib.request.build_opener()
+url_orig_headers = url_opener.addheaders
+urllib.request.install_opener(url_opener)
class RecipeMeta(type):
@@ -48,10 +36,10 @@ class RecipeMeta(type):
if 'version' in dct:
dct['_version'] = dct.pop('version')
- return super(RecipeMeta, cls).__new__(cls, name, bases, dct)
+ return super().__new__(cls, name, bases, dct)
-class Recipe(with_metaclass(RecipeMeta)):
+class Recipe(metaclass=RecipeMeta):
_url = None
'''The address from which the recipe may be downloaded. This is not
essential, it may be omitted if the source is available some other
@@ -76,6 +64,18 @@ class Recipe(with_metaclass(RecipeMeta)):
finished correctly.
'''
+ sha512sum = None
+ '''The sha512sum of the source from the :attr:`url`. Non-essential, but
+ you should try to include this, it is used to check that the download
+ finished correctly.
+ '''
+
+ blake2bsum = None
+ '''The blake2bsum of the source from the :attr:`url`. Non-essential, but
+ you should try to include this, it is used to check that the download
+ finished correctly.
+ '''
+
depends = []
'''A list containing the names of any recipes that this recipe depends on.
'''
@@ -102,6 +102,50 @@ class Recipe(with_metaclass(RecipeMeta)):
archs = ['armeabi'] # Not currently implemented properly
+ built_libraries = {}
+ """Each recipe that builds a system library (e.g.:libffi, openssl, etc...)
+ should contain a dict holding the relevant information of the library. The
+ keys should be the generated libraries and the values the relative path of
+ the library inside his build folder. This dict will be used to perform
+ different operations:
+ - copy the library into the right location, depending on if it's shared
+ or static)
+ - check if we have to rebuild the library
+
+ Here an example of how it would look like for `libffi` recipe:
+
+ - `built_libraries = {'libffi.so': '.libs'}`
+
+ .. note:: in case that the built library resides in recipe's build
+ directory, you can set the following values for the relative
+ path: `'.', None or ''`
+ """
+
+ need_stl_shared = False
+ '''Some libraries or python packages may need the c++_shared in APK.
+ We can automatically do this for any recipe if we set this property to
+ `True`'''
+
+ stl_lib_name = 'c++_shared'
+ '''
+ The default STL shared lib to use: `c++_shared`.
+
+ .. note:: Android NDK version > 17 only supports 'c++_shared', because
+ starting from NDK r18 the `gnustl_shared` lib has been deprecated.
+ '''
+
+ def get_stl_library(self, arch):
+ return join(
+ arch.ndk_lib_dir,
+ 'lib{name}.so'.format(name=self.stl_lib_name),
+ )
+
+ def install_stl_lib(self, arch):
+ if not self.ctx.has_lib(
+ arch.arch, 'lib{name}.so'.format(name=self.stl_lib_name)
+ ):
+ self.install_libs(arch, self.get_stl_library(arch))
+
@property
def version(self):
key = 'VERSION_' + self.name
@@ -149,35 +193,46 @@ class Recipe(with_metaclass(RecipeMeta)):
# Download item with multiple attempts (for bad connections):
attempts = 0
+ seconds = 1
while True:
try:
+ # jqueryui.com returns a 403 w/ the default user agent
+ # Mozilla/5.0 doesnt handle redirection for liblzma
+ url_opener.addheaders = [('User-agent', 'Wget/1.0')]
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)
+ raise
+ stdout.write('Download failed: {}; retrying in {} second(s)...'.format(e, seconds))
+ time.sleep(seconds)
+ seconds *= 2
continue
+ finally:
+ url_opener.addheaders = url_orig_headers
break
return target
elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
- if isdir(target):
- with current_directory(target):
- shprint(sh.git, 'fetch', '--tags')
- if self.version:
- shprint(sh.git, 'checkout', self.version)
- shprint(sh.git, 'pull')
- shprint(sh.git, 'pull', '--recurse-submodules')
- shprint(sh.git, 'submodule', 'update', '--recursive')
- else:
+ if not isdir(target):
if url.startswith('git+'):
url = url[4:]
- shprint(sh.git, 'clone', '--recursive', url, target)
+ # if 'version' is specified, do a shallow clone
if self.version:
+ shprint(sh.mkdir, '-p', target)
with current_directory(target):
- shprint(sh.git, 'checkout', self.version)
- shprint(sh.git, 'submodule', 'update', '--recursive')
+ shprint(sh.git, 'init')
+ shprint(sh.git, 'remote', 'add', 'origin', url)
+ else:
+ shprint(sh.git, 'clone', '--recursive', url, target)
+ with current_directory(target):
+ if self.version:
+ shprint(sh.git, 'fetch', '--depth', '1', 'origin', self.version)
+ shprint(sh.git, 'checkout', self.version)
+ branch = sh.git('branch', '--show-current')
+ if branch:
+ shprint(sh.git, 'pull')
+ shprint(sh.git, 'pull', '--recurse-submodules')
+ shprint(sh.git, 'submodule', 'update', '--recursive', '--init', '--depth', '1')
return target
def apply_patch(self, filename, arch, build_dir=None):
@@ -298,16 +353,19 @@ class Recipe(with_metaclass(RecipeMeta)):
return
url = self.versioned_url
- ma = match(u'^(.+)#md5=([0-9a-f]{32})$', url)
- if ma: # fragmented URL?
- if self.md5sum:
- raise ValueError(
- ('Received md5sum from both the {} recipe '
- 'and its url').format(self.name))
- url = ma.group(1)
- expected_md5 = ma.group(2)
- else:
- expected_md5 = self.md5sum
+ expected_digests = {}
+ for alg in set(hashlib.algorithms_guaranteed) | set(('md5', 'sha512', 'blake2b')):
+ expected_digest = getattr(self, alg + 'sum') if hasattr(self, alg + 'sum') else None
+ ma = match(u'^(.+)#' + alg + u'=([0-9a-f]{32,})$', url)
+ if ma: # fragmented URL?
+ if expected_digest:
+ raise ValueError(
+ ('Received {}sum from both the {} recipe '
+ 'and its url').format(alg, self.name))
+ url = ma.group(1)
+ expected_digest = ma.group(2)
+ if expected_digest:
+ expected_digests[alg] = expected_digest
shprint(sh.mkdir, '-p', join(self.ctx.packages_path, self.name))
@@ -319,16 +377,17 @@ class Recipe(with_metaclass(RecipeMeta)):
if exists(filename) and isfile(filename):
if not exists(marker_filename):
shprint(sh.rm, filename)
- elif expected_md5:
- current_md5 = md5sum(filename)
- if current_md5 != expected_md5:
- debug('* Generated md5sum: {}'.format(current_md5))
- debug('* Expected md5sum: {}'.format(expected_md5))
- raise ValueError(
- ('Generated md5sum does not match expected md5sum '
- 'for {} recipe').format(self.name))
- do_download = False
else:
+ for alg, expected_digest in expected_digests.items():
+ current_digest = algsum(alg, filename)
+ if current_digest != expected_digest:
+ debug('* Generated {}sum: {}'.format(alg,
+ current_digest))
+ debug('* Expected {}sum: {}'.format(alg,
+ expected_digest))
+ raise ValueError(
+ ('Generated {0}sum does not match expected {0}sum '
+ 'for {1} recipe').format(alg, self.name))
do_download = False
# If we got this far, we will download
@@ -339,15 +398,17 @@ class Recipe(with_metaclass(RecipeMeta)):
self.download_file(self.versioned_url, filename)
shprint(sh.touch, marker_filename)
- if exists(filename) and isfile(filename) and expected_md5:
- current_md5 = md5sum(filename)
- if expected_md5 is not None:
- if current_md5 != expected_md5:
- debug('* Generated md5sum: {}'.format(current_md5))
- debug('* Expected md5sum: {}'.format(expected_md5))
+ if exists(filename) and isfile(filename):
+ for alg, expected_digest in expected_digests.items():
+ current_digest = algsum(alg, filename)
+ if current_digest != expected_digest:
+ debug('* Generated {}sum: {}'.format(alg,
+ current_digest))
+ debug('* Expected {}sum: {}'.format(alg,
+ expected_digest))
raise ValueError(
- ('Generated md5sum does not match expected md5sum '
- 'for {} recipe').format(self.name))
+ ('Generated {0}sum does not match expected {0}sum '
+ 'for {1} recipe').format(alg, self.name))
else:
info('{} download already cached, skipping'.format(self.name))
@@ -375,7 +436,7 @@ class Recipe(with_metaclass(RecipeMeta)):
filename = shprint(
sh.basename, self.versioned_url).stdout[:-1].decode('utf-8')
- ma = match(u'^(.+)#md5=([0-9a-f]{32})$', filename)
+ ma = match(u'^(.+)#[a-z0-9_]{3,}=([0-9a-f]{32,})$', filename)
if ma: # fragmented URL?
filename = ma.group(1)
@@ -405,7 +466,7 @@ class Recipe(with_metaclass(RecipeMeta)):
sh.tar('xf', extraction_filename)
root_directory = sh.tar('tf', extraction_filename).stdout.decode(
'utf-8').split('\n')[0].split('/')[0]
- if root_directory != directory_name:
+ if root_directory != basename(directory_name):
shprint(sh.mv, root_directory, directory_name)
else:
raise Exception(
@@ -426,12 +487,13 @@ 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, clang=False):
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
"""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, clang=clang)
+ env = arch.get_env(with_flags_in_cc=with_flags_in_cc)
+ return env
def prebuild_arch(self, arch):
'''Run any pre-build tasks for the Recipe. By default, this checks if
@@ -475,9 +537,14 @@ class Recipe(with_metaclass(RecipeMeta)):
def should_build(self, arch):
'''Should perform any necessary test and return True only if it needs
- building again.
+ building again. Per default we implement a library test, in case that
+ we detect so.
'''
+ if self.built_libraries:
+ return not all(
+ exists(lib) for lib in self.get_libraries(arch.arch)
+ )
return True
def build_arch(self, arch):
@@ -488,6 +555,19 @@ class Recipe(with_metaclass(RecipeMeta)):
if hasattr(self, build):
getattr(self, build)()
+ def install_libraries(self, arch):
+ '''This method is always called after `build_arch`. In case that we
+ detect a library recipe, defined by the class attribute
+ `built_libraries`, we will copy all defined libraries into the
+ right location.
+ '''
+ if not self.built_libraries:
+ return
+ shared_libs = [
+ lib for lib in self.get_libraries(arch) if lib.endswith(".so")
+ ]
+ self.install_libs(arch, *shared_libs)
+
def postbuild_arch(self, arch):
'''Run any post-build tasks for the Recipe. By default, this checks if
any postbuild_archname methods exist for the archname of the
@@ -497,6 +577,9 @@ class Recipe(with_metaclass(RecipeMeta)):
if hasattr(self, postbuild):
getattr(self, postbuild)()
+ if self.need_stl_shared:
+ self.install_stl_lib(arch)
+
def prepare_build_dir(self, arch):
'''Copies the recipe data into a build dir for the given arch. By
default, this unpacks a downloaded recipe. You should override
@@ -548,7 +631,28 @@ class Recipe(with_metaclass(RecipeMeta)):
shprint(sh.cp, *args)
def has_libs(self, arch, *libs):
- return all(map(lambda l: self.ctx.has_lib(arch.arch, l), libs))
+ return all(map(lambda lib: self.ctx.has_lib(arch.arch, lib), libs))
+
+ def get_libraries(self, arch_name, in_context=False):
+ """Return the full path of the library depending on the architecture.
+ Per default, the build library path it will be returned, unless
+ `get_libraries` has been called with kwarg `in_context` set to
+ True.
+
+ .. note:: this method should be used for library recipes only
+ """
+ recipe_libs = set()
+ if not self.built_libraries:
+ return recipe_libs
+ for lib, rel_path in self.built_libraries.items():
+ if not in_context:
+ abs_path = join(self.get_build_dir(arch_name), rel_path, lib)
+ if rel_path in {".", "", None}:
+ abs_path = join(self.get_build_dir(arch_name), lib)
+ else:
+ abs_path = join(self.ctx.get_libs_dir(arch_name), lib)
+ recipe_libs.add(abs_path)
+ return recipe_libs
@classmethod
def recipe_dirs(cls, ctx):
@@ -596,7 +700,7 @@ class Recipe(with_metaclass(RecipeMeta)):
if recipe_file is not None:
break
- if not recipe_file:
+ else:
raise ValueError('Recipe does not exist: {}'.format(name))
mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
@@ -649,17 +753,14 @@ class BootstrapNDKRecipe(Recipe):
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)
+ env = super().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'
+ self.ctx.python_recipe.link_version)
return env
@@ -684,13 +785,14 @@ class NDKRecipe(Recipe):
return join(self.get_build_dir(arch.arch), 'jni')
def build_arch(self, arch, *extra_args):
- super(NDKRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
shprint(
- sh.ndk_build,
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
'V=1',
+ 'NDK_DEBUG=' + ("1" if self.ctx.build_as_debuggable else "0"),
'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
'APP_ABI=' + arch.arch,
*extra_args, _env=env
@@ -721,17 +823,35 @@ class PythonRecipe(Recipe):
This is almost always what you want to do.'''
setup_extra_args = []
- '''List of extra arugments to pass to setup.py'''
+ '''List of extra arguments to pass to setup.py'''
+
+ depends = ['python3']
+ '''
+ .. note:: it's important to keep this depends as a class attribute outside
+ `__init__` because sometimes we only initialize the class, so the
+ `__init__` call won't be called and the deps would be missing
+ (which breaks the dependency graph computation)
+
+ .. warning:: don't forget to call `super().__init__()` in any recipe's
+ `__init__`, or otherwise it may not be ensured that it depends
+ on python2 or python3 which can break the dependency graph
+ '''
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
+ super().__init__(*args, **kwargs)
+
+ if 'python3' not in self.depends:
+ # We ensure here that the recipe depends on python even it overrode
+ # `depends`. We only do this if it doesn't already depend on any
+ # python, since some recipes intentionally don't depend on/work
+ # with all python variants
+ depends = self.depends
+ depends.append('python3')
+ depends = list(set(depends))
+ self.depends = depends
def clean_build(self, arch=None):
- super(PythonRecipe, self).clean_build(arch=arch)
+ super().clean_build(arch=arch)
name = self.folder_name
python_install_dirs = glob.glob(join(self.ctx.python_installs_dir, '*'))
for python_install in python_install_dirs:
@@ -746,11 +866,9 @@ class PythonRecipe(Recipe):
@property
def real_hostpython_location(self):
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')
+ if host_name == 'hostpython3':
+ python_recipe = Recipe.get_recipe(host_name, self.ctx)
+ return python_recipe.python_exe
else:
python_recipe = self.ctx.python_recipe
return 'python{}'.format(python_recipe.version)
@@ -770,7 +888,7 @@ class PythonRecipe(Recipe):
return name
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(PythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
env['PYTHONNOUSERSITE'] = '1'
@@ -779,31 +897,13 @@ class PythonRecipe(Recipe):
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 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(
- join(ndk_dir_python, 'include',
- 'python'))
- env['LDFLAGS'] += ' -L{}'.format(
- join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}'.format(python_short_version)
+ 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.link_version,
+ )
hppath = []
hppath.append(join(dirname(self.hostpython_location), 'Lib'))
@@ -821,7 +921,7 @@ class PythonRecipe(Recipe):
def should_build(self, arch):
name = self.folder_name
- if self.ctx.has_package(name):
+ if self.ctx.has_package(name, arch):
info('Python package already exists in site-packages')
return False
info('{} apparently isn\'t already in site-packages'.format(name))
@@ -830,7 +930,7 @@ class PythonRecipe(Recipe):
def build_arch(self, arch):
'''Install the Python module by calling setup.py install with
the target Python dir.'''
- super(PythonRecipe, self).build_arch(arch)
+ super().build_arch(arch)
self.install_python_package(arch)
def install_python_package(self, arch, name=None, env=None, is_dir=True):
@@ -844,29 +944,13 @@ class PythonRecipe(Recipe):
info('Installing {} into site-packages'.format(self.name))
+ hostpython = sh.Command(self.hostpython_location)
+ hpenv = env.copy()
with current_directory(self.get_build_dir(arch.arch)):
- hostpython = sh.Command(self.hostpython_location)
-
- if self.ctx.python_recipe.name != 'python2legacy':
- hpenv = env.copy()
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=.',
- _env=hpenv, *self.setup_extra_args)
- elif self.call_hostpython_via_targetpython:
- shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
- *self.setup_extra_args)
- 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(':'))
- 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',
- _env=hpenv, *self.setup_extra_args)
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
+ '--install-lib=.',
+ _env=hpenv, *self.setup_extra_args)
# If asked, also install in the hostpython build dir
if self.install_in_hostpython:
@@ -903,8 +987,8 @@ class CompiledComponentsPythonRecipe(PythonRecipe):
info('Building compiled components in {}'.format(self.name))
env = self.get_recipe_env(arch)
+ hostpython = sh.Command(self.hostpython_location)
with current_directory(self.get_build_dir(arch.arch)):
- hostpython = sh.Command(self.hostpython_location)
if self.install_in_hostpython:
shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)
shprint(hostpython, 'setup.py', self.build_cmd, '-v',
@@ -916,7 +1000,7 @@ class CompiledComponentsPythonRecipe(PythonRecipe):
def install_hostpython_package(self, arch):
env = self.get_hostrecipe_env(arch)
self.rebuild_compiled_components(arch, env)
- super(CompiledComponentsPythonRecipe, self).install_hostpython_package(arch)
+ super().install_hostpython_package(arch)
def rebuild_compiled_components(self, arch, env):
info('Rebuilding compiled components in {}'.format(self.name))
@@ -930,35 +1014,7 @@ class CompiledComponentsPythonRecipe(PythonRecipe):
class CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe):
""" Extensions that require the cxx-stl """
call_hostpython_via_targetpython = False
-
- def get_recipe_env(self, arch):
- env = super(CppCompiledComponentsPythonRecipe, self).get_recipe_env(arch)
- keys = dict(
- ctx=self.ctx,
- arch=arch,
- 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['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)
-
- return env
-
- def build_compiled_components(self, arch):
- super(CppCompiledComponentsPythonRecipe, self).build_compiled_components(arch)
-
- # 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),
- self.ctx.get_libs_dir(arch.arch)
- )
+ need_stl_shared = True
class CythonRecipe(PythonRecipe):
@@ -967,13 +1023,6 @@ class CythonRecipe(PythonRecipe):
cython_args = []
call_hostpython_via_targetpython = False
- def __init__(self, *args, **kwargs):
- super(CythonRecipe, self).__init__(*args, **kwargs)
- depends = self.depends
- depends.append(('python2', 'python2legacy', 'python3', 'python3crystax'))
- depends = list(set(depends))
- self.depends = depends
-
def build_arch(self, arch):
'''Build any cython components, then install the Python module by
calling setup.py install with the target Python dir.
@@ -1011,25 +1060,20 @@ class CythonRecipe(PythonRecipe):
info('First build appeared to complete correctly, skipping manual'
'cythonising.')
- self.strip_object_files(arch, env)
+ if not self.ctx.with_debug_symbols:
+ 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)
- else:
- shprint(sh.find, '.', '-iname', '*.so', '-exec',
- '/usr/bin/echo', '{}', ';', _env=env)
- shprint(sh.find, '.', '-iname', '*.so', '-exec',
- env['STRIP'].split(' ')[0], '--strip-unneeded',
- # '/usr/bin/strip', '--strip-unneeded',
- '{}', ';', _env=env)
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ '/usr/bin/echo', '{}', ';', _env=env)
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ env['STRIP'].split(' ')[0], '--strip-unneeded',
+ # '/usr/bin/strip', '--strip-unneeded',
+ '{}', ';', _env=env)
def cythonize_file(self, env, build_dir, filename):
short_filename = filename
@@ -1043,9 +1087,12 @@ class CythonRecipe(PythonRecipe):
del cyenv['PYTHONPATH']
if 'PYTHONNOUSERSITE' in cyenv:
cyenv.pop('PYTHONNOUSERSITE')
- cython = 'cython' if self.ctx.python_recipe.from_crystax else self.ctx.cython
- cython_command = sh.Command(cython)
- shprint(cython_command, filename, *self.cython_args, _env=cyenv)
+ python_command = sh.Command("python{}".format(
+ self.ctx.python_recipe.major_minor_version_string.split(".")[0]
+ ))
+ shprint(python_command, "-c"
+ "import sys; from Cython.Compiler.Main import setuptools_main; sys.exit(setuptools_main());",
+ filename, *self.cython_args, _env=cyenv)
def cythonize_build(self, env, build_dir="."):
if not self.cythonize:
@@ -1057,23 +1104,16 @@ class CythonRecipe(PythonRecipe):
self.cythonize_file(env, build_dir, join(root, filename))
def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(CythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(
self.ctx.get_libs_dir(arch.arch) +
' -L{} '.format(self.ctx.libs_dir) +
' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',
arch.arch)))
- if self.ctx.python_recipe.from_crystax:
- env['LDFLAGS'] = (env['LDFLAGS'] +
- ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)))
- if self.ctx.python_recipe.name == 'python2legacy':
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh')
- else:
- env['LDSHARED'] = env['CC'] + ' -shared'
+ env['LDSHARED'] = env['CC'] + ' -shared'
# shprint(sh.whereis, env['LDSHARED'], _env=env)
env['LIBLINK'] = 'NOTNONE'
- env['NDKPLATFORM'] = self.ctx.ndk_platform
if self.ctx.copy_libs:
env['COPYLIBS'] = '1'
@@ -1084,24 +1124,6 @@ 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
@@ -1109,20 +1131,12 @@ class TargetPythonRecipe(Recipe):
'''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.'''
- from_crystax = False
- '''True if the python is used from CrystaX, False otherwise (i.e. if
- it is built by p4a).'''
-
def __init__(self, *args, **kwargs):
self._ctx = None
- super(TargetPythonRecipe, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
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))
+ super().prebuild_arch(arch)
self.ctx.python_recipe = self
def include_root(self, arch):
@@ -1160,10 +1174,10 @@ class TargetPythonRecipe(Recipe):
shprint(sh.mv, filen, join(file_dirname, parts[0] + '.so'))
-def md5sum(filen):
- '''Calculate the md5sum of a file.
+def algsum(alg, filen):
+ '''Calculate the digest of a file.
'''
with open(filen, 'rb') as fileh:
- md5 = hashlib.md5(fileh.read())
+ digest = getattr(hashlib, alg)(fileh.read())
- return md5.hexdigest()
+ return digest.hexdigest()
diff --git a/p4a/pythonforandroid/recipes/Pillow/__init__.py b/p4a/pythonforandroid/recipes/Pillow/__init__.py
index 14c9d2b..f8f6929 100644
--- a/p4a/pythonforandroid/recipes/Pillow/__init__.py
+++ b/p4a/pythonforandroid/recipes/Pillow/__init__.py
@@ -1,28 +1,43 @@
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
from os.path import join
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
class PillowRecipe(CompiledComponentsPythonRecipe):
+ """
+ A recipe for Pillow (previously known as Pil).
- version = '5.2.0'
+ This recipe allow us to build the Pillow recipe with support for different
+ types of images and fonts. But you should be aware, that in order to use
+ some of the features of Pillow, we must build some libraries. By default
+ we automatically trigger the build of below libraries::
+
+ - freetype: rendering fonts support.
+ - harfbuzz: a text shaping library.
+ - jpeg: reading and writing JPEG image files.
+ - png: support for PNG images.
+
+ But you also could enable the build of some extra image types by requesting
+ the build of some libraries via argument `requirements`::
+
+ - libwebp: library to encode and decode images in WebP format.
+ """
+
+ version = '8.4.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')]
+ opt_depends = ['libwebp']
+ patches = [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')
+ env = super().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)
+ png_lib_dir = join(png.get_build_dir(arch.arch), '.libs')
+ png_inc_dir = png.get_build_dir(arch)
jpeg = self.get_recipe('jpeg', self.ctx)
jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
@@ -37,22 +52,38 @@ class PillowRecipe(CompiledComponentsPythonRecipe):
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)
+ # libwebp is an optional dependency, so we add the
+ # flags if we have it in our `ctx.recipe_build_order`
+ build_with_webp_support = 'libwebp' in self.ctx.recipe_build_order
+ if build_with_webp_support:
+ webp = self.get_recipe('libwebp', self.ctx)
+ webp_install = join(
+ webp.get_build_dir(arch.arch), 'installation'
+ )
- 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)
+ # Add libraries includes to CFLAGS
+ cflags = f' -I{png_inc_dir}'
+ cflags += f' -I{harf_inc_dir} -I{join(harf_inc_dir, "src")}'
+ cflags += f' -I{free_inc_dir}'
+ cflags += f' -I{jpeg_inc_dir}'
+ if build_with_webp_support:
+ cflags += f' -I{join(webp_install, "include")}'
+ cflags += f' -I{self.ctx.ndk.sysroot_include_dir}'
- env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg'
+ # Link the basic Pillow libraries...no need to add webp's libraries
+ # since it seems that the linkage is properly made without it :)
+ env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg -lm'
- env['LDFLAGS'] += ' -L{} -L{} -L{} -L{}'.format(
- png_lib_dir, harf_lib_dir, jpeg_lib_dir, ndk_lib_dir)
+ # Add libraries locations to LDFLAGS
+ env['LDFLAGS'] += f' -L{png_lib_dir}'
+ env['LDFLAGS'] += f' -L{free_lib_dir}'
+ env['LDFLAGS'] += f' -L{harf_lib_dir}'
+ env['LDFLAGS'] += f' -L{jpeg_lib_dir}'
+ if build_with_webp_support:
+ env['LDFLAGS'] += f' -L{join(webp_install, "lib")}'
+ env['LDFLAGS'] += f' -L{arch.ndk_lib_dir_versioned}'
if cflags not in env['CFLAGS']:
- env['CFLAGS'] += cflags
+ env['CFLAGS'] += cflags + " -lm"
return env
diff --git a/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch b/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch
deleted file mode 100644
index ee22e98..0000000
--- a/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-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
index 3b0ccef..5c5a3d0 100644
--- a/p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
+++ b/p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
@@ -1,148 +1,196 @@
-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):
+--- Pillow.orig/setup.py 2021-11-01 14:50:48.000000000 +0100
++++ Pillow/setup.py 2021-11-01 14:51:31.000000000 +0100
+@@ -125,7 +125,7 @@
+ "codec_fd",
+ )
- 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
+-DEBUG = False
++DEBUG = True # So we can easely triage user issues.
-@@ -194,7 +194,7 @@ class pil_build_ext(build_ext):
- ]
+ class DependencyException(Exception):
+@@ -411,46 +411,6 @@
+ include_dirs = []
- 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
+ pkg_config = None
+- if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")):
+- pkg_config = _pkg_config
-
-- # Find the best version
-- for directory in self.compiler.include_dirs:
-- _dbg('Checking for openjpeg-#.# in %s', directory)
+- #
+- # add configured kits
+- for root_name, lib_name in dict(
+- JPEG_ROOT="libjpeg",
+- JPEG2K_ROOT="libopenjp2",
+- TIFF_ROOT=("libtiff-5", "libtiff-4"),
+- ZLIB_ROOT="zlib",
+- FREETYPE_ROOT="freetype2",
+- HARFBUZZ_ROOT="harfbuzz",
+- FRIBIDI_ROOT="fribidi",
+- LCMS_ROOT="lcms2",
+- IMAGEQUANT_ROOT="libimagequant",
+- ).items():
+- root = globals()[root_name]
+-
+- if root is None and root_name in os.environ:
+- prefix = os.environ[root_name]
+- root = (os.path.join(prefix, "lib"), os.path.join(prefix, "include"))
+-
+- if root is None and pkg_config:
+- if isinstance(lib_name, tuple):
+- for lib_name2 in lib_name:
+- _dbg(f"Looking for `{lib_name2}` using pkg-config.")
+- root = pkg_config(lib_name2)
+- if root:
+- break
+- else:
+- _dbg(f"Looking for `{lib_name}` using pkg-config.")
+- root = pkg_config(lib_name)
+-
+- if isinstance(root, tuple):
+- lib_root, include_root = root
+- else:
+- lib_root = include_root = root
+-
+- _add_directory(library_dirs, lib_root)
+- _add_directory(include_dirs, include_root)
+
+ # respect CFLAGS/CPPFLAGS/LDFLAGS
+ for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
+@@ -471,137 +431,6 @@
+ for d in os.environ[k].split(os.path.pathsep):
+ _add_directory(library_dirs, d)
+
+- _add_directory(library_dirs, os.path.join(sys.prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(sys.prefix, "include"))
+-
+- #
+- # add platform directories
+-
+- if self.disable_platform_guessing:
+- pass
+-
+- elif sys.platform == "cygwin":
+- # pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
+- _add_directory(
+- library_dirs,
+- os.path.join(
+- "/usr/lib", "python{}.{}".format(*sys.version_info), "config"
+- ),
+- )
+-
+- elif sys.platform == "darwin":
+- # attempt to make sure we pick freetype2 over other versions
+- _add_directory(include_dirs, "/sw/include/freetype2")
+- _add_directory(include_dirs, "/sw/lib/freetype2/include")
+- # fink installation directories
+- _add_directory(library_dirs, "/sw/lib")
+- _add_directory(include_dirs, "/sw/include")
+- # darwin ports installation directories
+- _add_directory(library_dirs, "/opt/local/lib")
+- _add_directory(include_dirs, "/opt/local/include")
+-
+- # if Homebrew is installed, use its lib and include directories
+- try:
+- prefix = (
+- subprocess.check_output(["brew", "--prefix"])
+- .strip()
+- .decode("latin1")
+- )
+- except Exception:
+- # Homebrew not installed
+- prefix = None
+-
+- ft_prefix = None
+-
+- if prefix:
+- # add Homebrew's include and lib directories
+- _add_directory(library_dirs, os.path.join(prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(prefix, "include"))
+- _add_directory(
+- include_dirs, os.path.join(prefix, "opt", "zlib", "include")
+- )
+- ft_prefix = os.path.join(prefix, "opt", "freetype")
+-
+- if ft_prefix and os.path.isdir(ft_prefix):
+- # freetype might not be linked into Homebrew's prefix
+- _add_directory(library_dirs, os.path.join(ft_prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(ft_prefix, "include"))
+- else:
+- # fall back to freetype from XQuartz if
+- # Homebrew's freetype is missing
+- _add_directory(library_dirs, "/usr/X11/lib")
+- _add_directory(include_dirs, "/usr/X11/include")
+-
+- # SDK install path
+- sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
+- if not os.path.exists(sdk_path):
- try:
-- listdir = os.listdir(directory)
+- sdk_path = (
+- subprocess.check_output(["xcrun", "--show-sdk-path"])
+- .strip()
+- .decode("latin1")
+- )
- 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))
+- sdk_path = None
+- if sdk_path:
+- _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib"))
+- _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include"))
+- elif (
+- sys.platform.startswith("linux")
+- or sys.platform.startswith("gnu")
+- or sys.platform.startswith("freebsd")
+- ):
+- for dirname in _find_library_dirs_ldconfig():
+- _add_directory(library_dirs, dirname)
+- if sys.platform.startswith("linux") and os.environ.get(
+- "ANDROID_ROOT", None
+- ):
+- # termux support for android.
+- # system libraries (zlib) are installed in /system/lib
+- # headers are at $PREFIX/include
+- # user libs are at $PREFIX/lib
+- _add_directory(
+- library_dirs, os.path.join(os.environ["ANDROID_ROOT"], "lib")
+- )
-
-- 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)
+- elif sys.platform.startswith("netbsd"):
+- _add_directory(library_dirs, "/usr/pkg/lib")
+- _add_directory(include_dirs, "/usr/pkg/include")
-
-- 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"
+- elif sys.platform.startswith("sunos5"):
+- _add_directory(library_dirs, "/opt/local/lib")
+- _add_directory(include_dirs, "/opt/local/include")
-
-- 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"
+- # FIXME: check /opt/stuff directories here?
-
-- 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"
+- # standard locations
+- if not self.disable_platform_guessing:
+- _add_directory(library_dirs, "/usr/local/lib")
+- _add_directory(include_dirs, "/usr/local/include")
-
-- 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"
+- _add_directory(library_dirs, "/usr/lib")
+- _add_directory(include_dirs, "/usr/include")
+- # alpine, at least
+- _add_directory(library_dirs, "/lib")
-
- 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"]))
+- if sys.platform == "win32":
+- # on Windows, look for the OpenJPEG libraries in the location that
+- # the official installer puts them
+- program_files = os.environ.get("ProgramFiles", "")
+- best_version = (0, 0)
+- best_path = None
+- for name in os.listdir(program_files):
+- if name.startswith("OpenJPEG "):
+- version = tuple(int(x) for x in name[9:].strip().split("."))
+- if version > best_version:
+- best_version = version
+- best_path = os.path.join(program_files, name)
+-
+- if best_path:
+- _dbg("Adding %s to search list", best_path)
+- _add_directory(library_dirs, os.path.join(best_path, "lib"))
+- _add_directory(include_dirs, os.path.join(best_path, "include"))
+-
+ #
+ # insert new dirs *before* default libs, to avoid conflicts
+ # between Python PYD stub libs and real libraries
\ No newline at end of file
diff --git a/p4a/pythonforandroid/recipes/aiohttp/__init__.py b/p4a/pythonforandroid/recipes/aiohttp/__init__.py
new file mode 100644
index 0000000..f32c653
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/aiohttp/__init__.py
@@ -0,0 +1,20 @@
+"""Build AIOHTTP"""
+from typing import List
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class AIOHTTPRecipe(CppCompiledComponentsPythonRecipe): # type: ignore # pylint: disable=R0903
+ version = "3.8.3"
+ url = "https://pypi.python.org/packages/source/a/aiohttp/aiohttp-{version}.tar.gz"
+ name = "aiohttp"
+ depends: List[str] = ["setuptools"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['LDFLAGS'] += ' -lc++_shared'
+ return env
+
+
+recipe = AIOHTTPRecipe()
diff --git a/p4a/pythonforandroid/recipes/android/__init__.py b/p4a/pythonforandroid/recipes/android/__init__.py
index 4a06ca8..d290a1a 100644
--- a/p4a/pythonforandroid/recipes/android/__init__.py
+++ b/p4a/pythonforandroid/recipes/android/__init__.py
@@ -1,7 +1,5 @@
-from __future__ import unicode_literals
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
from pythonforandroid.util import current_directory
-from pythonforandroid.patching import will_build
from pythonforandroid import logger
from os.path import join
@@ -14,18 +12,17 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
src_filename = 'src'
- depends = [('pygame', 'sdl2', 'genericndkbuild'),
- 'pyjnius']
+ depends = [('sdl2', 'genericndkbuild'), 'pyjnius']
config_env = {}
def get_recipe_env(self, arch):
- env = super(AndroidRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env.update(self.config_env)
return env
def prebuild_arch(self, arch):
- super(AndroidRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
ctx_bootstrap = self.ctx.bootstrap.name
# define macros for Cython, C, Python
@@ -37,19 +34,11 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
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'
+ is_lbry = bootstrap_name in ('lbry',)
+ is_sdl2 = (bootstrap_name == "sdl2")
+ if bootstrap_name in ["sdl2", "webview", "service_only", "service_library", "lbry"]:
java_ns = u'org.kivy.android'
jni_ns = u'org/kivy/android'
- elif is_pygame:
- java_ns = u'org.renpy.android'
- jni_ns = u'org/renpy/android'
else:
logger.error((
'unsupported bootstrap for android recipe: {}'
@@ -60,10 +49,12 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
config = {
'BOOTSTRAP': bootstrap,
'IS_SDL2': int(is_sdl2),
- 'IS_PYGAME': int(is_pygame),
- 'PY2': int(will_build('python2')(self)),
+ 'PY2': 0,
'JAVA_NAMESPACE': java_ns,
'JNI_NAMESPACE': jni_ns,
+ 'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name,
+ 'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'),
+ 'SERVICE_CLASS_NAME': self.ctx.service_class_name,
}
# create config files for Cython, C and Python
@@ -88,8 +79,11 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
fh.write(
'#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
)
- elif is_pygame:
- fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
+ else:
+ fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv WebView_AndroidGetJNIEnv\n'
+ )
recipe = AndroidRecipe()
diff --git a/p4a/pythonforandroid/recipes/android/src/android/_android.pyx b/p4a/pythonforandroid/recipes/android/src/android/_android.pyx
index d332eed..6708b84 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/_android.pyx
+++ b/p4a/pythonforandroid/recipes/android/src/android/_android.pyx
@@ -2,22 +2,6 @@
include "config.pxi"
-IF BOOTSTRAP == 'pygame':
- cdef extern int SDL_ANDROID_CheckPause()
- cdef extern void SDL_ANDROID_WaitForResume() nogil
- cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
-
- def check_pause():
- return SDL_ANDROID_CheckPause()
-
- def wait_for_resume():
- android_accelerometer_enable(False)
- SDL_ANDROID_WaitForResume()
- android_accelerometer_enable(accelerometer_enabled)
-
- def map_key(scancode, keysym):
- SDL_ANDROID_MapKey(scancode, keysym)
-
# Android keycodes.
KEYCODE_UNKNOWN = 0
KEYCODE_SOFT_LEFT = 1
@@ -175,12 +159,10 @@ api_version = autoclass('android.os.Build$VERSION').SDK_INT
version_codes = autoclass('android.os.Build$VERSION_CODES')
-python_act = autoclass(JAVA_NAMESPACE + u'.PythonActivity')
+python_act = autoclass(ACTIVITY_CLASS_NAME)
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 not need the listener so there is
# no point adding a processor intensive layout listenere here.
height = 0
@@ -274,42 +256,6 @@ def get_buildinfo():
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
return binfo
-IF IS_PYGAME:
- # Activate input - required to receive input events.
- cdef extern void android_activate_input()
-
- def init():
- android_activate_input()
-
- # Action send
- cdef extern void android_action_send(char*, char*, char*, char*, char*)
- def action_send(mimetype, filename=None, subject=None, text=None,
- chooser_title=None):
- cdef char *j_mimetype = mimetype
- cdef char *j_filename = NULL
- cdef char *j_subject = NULL
- cdef char *j_text = NULL
- cdef char *j_chooser_title = NULL
- if filename is not None:
- j_filename = filename
- if subject is not None:
- j_subject = subject
- if text is not None:
- j_text = text
- if chooser_title is not None:
- j_chooser_title = chooser_title
- android_action_send(j_mimetype, j_filename, j_subject, j_text,
- j_chooser_title)
-
- cdef extern int android_checkstop()
- cdef extern void android_ackstop()
-
- def check_stop():
- return android_checkstop()
-
- def ack_stop():
- android_ackstop()
-
# -------------------------------------------------------------------
# URL Opening.
def open_url(url):
@@ -334,17 +280,29 @@ class AndroidBrowser(object):
import webbrowser
webbrowser.register('android', AndroidBrowser)
-cdef extern void android_start_service(char *, char *, char *)
-def start_service(title=None, description=None, arg=None):
- cdef char *j_title = NULL
- cdef char *j_description = NULL
- if title is not None:
- j_title = title
- if description is not None:
- j_description = description
- if arg is not None:
- j_arg = arg
- android_start_service(j_title, j_description, j_arg)
+
+def start_service(title="Background Service",
+ description="", arg="",
+ as_foreground=True):
+ # Legacy None value support (for old function signature style):
+ if title is None:
+ title = "Background Service"
+ if description is None:
+ description = ""
+ if arg is None:
+ arg = ""
+
+ # Start service:
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if as_foreground:
+ mActivity.start_service(
+ title, description, arg
+ )
+ else:
+ mActivity.start_service_not_as_foreground(
+ title, description, arg
+ )
+
cdef extern void android_stop_service()
def stop_service():
diff --git a/p4a/pythonforandroid/recipes/android/src/android/_android_billing.pyx b/p4a/pythonforandroid/recipes/android/src/android/_android_billing.pyx
index bd6bb2e..d5ed2a0 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/_android_billing.pyx
+++ b/p4a/pythonforandroid/recipes/android/src/android/_android_billing.pyx
@@ -15,7 +15,7 @@ class BillingService(object):
BILLING_TYPE_SUBSCRIPTION = 'subs'
def __init__(self, callback):
- super(BillingService, self).__init__()
+ super().__init__()
self.callback = callback
self.purchased_items = None
android_billing_service_start()
diff --git a/p4a/pythonforandroid/recipes/android/src/android/_android_jni.c b/p4a/pythonforandroid/recipes/android/src/android/_android_jni.c
index 8eee770..cf1b1bf 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/_android_jni.c
+++ b/p4a/pythonforandroid/recipes/android/src/android/_android_jni.c
@@ -201,146 +201,6 @@ void android_get_buildinfo() {
}
}
-#if IS_PYGAME
-void android_activate_input(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
- aassert(mid);
- }
-
- (*env)->CallStaticVoidMethod(env, cls, mid);
-}
-
-int android_checkstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
- aassert(mid);
- }
-
- return (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_ackstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
- aassert(mid);
- }
-
- (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/Action");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "send",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
- jstring j_filename = NULL;
- jstring j_subject = NULL;
- jstring j_text = NULL;
- jstring j_chooser_title = NULL;
- if ( filename != NULL )
- j_filename = (*env)->NewStringUTF(env, filename);
- if ( subject != NULL )
- j_subject = (*env)->NewStringUTF(env, subject);
- if ( text != NULL )
- j_text = (*env)->NewStringUTF(env, text);
- if ( chooser_title != NULL )
- j_chooser_title = (*env)->NewStringUTF(env, text);
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- j_mimeType, j_filename, j_subject, j_text,
- j_chooser_title);
-}
-
-void android_open_url(char *url) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
- aassert(mid);
- }
-
- PUSH_FRAME;
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- (*env)->NewStringUTF(env, url)
- );
-
- POP_FRAME;
-}
-#endif // IS_PYGAME
-
-void android_start_service(char *title, char *description, char *arg) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "start_service",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_title = NULL;
- jstring j_description = NULL;
- jstring j_arg = NULL;
- if ( title != 0 )
- j_title = (*env)->NewStringUTF(env, title);
- if ( description != 0 )
- j_description = (*env)->NewStringUTF(env, description);
- if ( arg != 0 )
- j_arg = (*env)->NewStringUTF(env, arg);
-
- (*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
-}
-
void android_stop_service() {
static JNIEnv *env = NULL;
static jclass *cls = NULL;
diff --git a/p4a/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py b/p4a/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py
new file mode 100644
index 0000000..a03512e
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py
@@ -0,0 +1,67 @@
+
+import sys
+import os
+
+
+def get_activity_lib_dir(activity_name):
+ from jnius import autoclass
+
+ # Get the actual activity instance:
+ activity_class = autoclass(activity_name)
+ if activity_class is None:
+ return None
+ activity = None
+ if hasattr(activity_class, "mActivity") and \
+ activity_class.mActivity is not None:
+ activity = activity_class.mActivity
+ elif hasattr(activity_class, "mService") and \
+ activity_class.mService is not None:
+ activity = activity_class.mService
+ if activity is None:
+ return None
+
+ # Extract the native lib dir from the activity instance:
+ package_name = activity.getApplicationContext().getPackageName()
+ manager = activity.getApplicationContext().getPackageManager()
+ manager_class = autoclass("android.content.pm.PackageManager")
+ native_lib_dir = manager.getApplicationInfo(
+ package_name, manager_class.GET_SHARED_LIBRARY_FILES
+ ).nativeLibraryDir
+ return native_lib_dir
+
+
+def does_libname_match_filename(search_name, file_path):
+ # Filter file names so given search_name="mymodule" we match one of:
+ # mymodule.so (direct name + .so)
+ # libmymodule.so (added lib prefix)
+ # mymodule.arm64.so (added dot-separated middle parts)
+ # mymodule.so.1.3.4 (added dot-separated version tail)
+ # and all above (all possible combinations)
+ import re
+ file_name = os.path.basename(file_path)
+ return (re.match(r"^(lib)?" + re.escape(search_name) +
+ r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)
+
+
+def find_library(name):
+ # Obtain all places for native libraries:
+ if sys.maxsize > 2**32: # 64bit-build
+ lib_search_dirs = ["/system/lib64", "/system/lib"]
+ else:
+ lib_search_dirs = ["/system/lib"]
+ lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
+ if lib_dir_1 is not None:
+ lib_search_dirs.insert(0, lib_dir_1)
+ lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
+ if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
+ lib_search_dirs.insert(0, lib_dir_2)
+
+ # Now scan the lib dirs:
+ for lib_dir in [ldir for ldir in lib_search_dirs if os.path.exists(ldir)]:
+ filelist = [
+ f for f in os.listdir(lib_dir)
+ if does_libname_match_filename(name, f)
+ ]
+ if len(filelist) > 0:
+ return os.path.join(lib_dir, filelist[0])
+ return None
diff --git a/p4a/pythonforandroid/recipes/android/src/android/activity.py b/p4a/pythonforandroid/recipes/android/src/android/activity.py
index cafbbda..78d068c 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/activity.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/activity.py
@@ -1,7 +1,7 @@
from jnius import PythonJavaClass, autoclass, java_method
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
-_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
+_activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
_callbacks = {
'on_new_intent': [],
@@ -10,11 +10,11 @@ _callbacks = {
class NewIntentListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']
__javacontext__ = 'app'
def __init__(self, callback, **kwargs):
- super(NewIntentListener, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.callback = callback
@java_method('(Landroid/content/Intent;)V')
@@ -23,11 +23,11 @@ class NewIntentListener(PythonJavaClass):
class ActivityResultListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']
__javacontext__ = 'app'
def __init__(self, callback):
- super(ActivityResultListener, self).__init__()
+ super().__init__()
self.callback = callback
@java_method('(IILandroid/content/Intent;)V')
@@ -61,3 +61,154 @@ def unbind(**kwargs):
_activity.unregisterNewIntentListener(listener)
elif event == 'on_activity_result':
_activity.unregisterActivityResultListener(listener)
+
+
+# Keep a reference to all the registered classes so that python doesn't
+# garbage collect them.
+_lifecycle_callbacks = set()
+
+
+class ActivityLifecycleCallbacks(PythonJavaClass):
+ """Callback class for handling PythonActivity lifecycle transitions"""
+
+ __javainterfaces__ = ['android/app/Application$ActivityLifecycleCallbacks']
+
+ def __init__(self, callbacks):
+ super().__init__()
+
+ # It would be nice to use keyword arguments, but PythonJavaClass
+ # doesn't allow that in its __cinit__ method.
+ if not isinstance(callbacks, dict):
+ raise ValueError('callbacks must be a dict instance')
+ self.callbacks = callbacks
+
+ def _callback(self, name, *args):
+ func = self.callbacks.get(name)
+ if func:
+ return func(*args)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityCreated(self, activity, savedInstanceState):
+ self._callback('onActivityCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityDestroyed(self, activity):
+ self._callback('onActivityDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPaused(self, activity):
+ self._callback('onActivityPaused', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPostCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostDestroyed(self, activity):
+ self._callback('onActivityPostDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostPaused(self, activity):
+ self._callback('onActivityPostPaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostResumed(self, activity):
+ self._callback('onActivityPostResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPostSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStarted(self, activity):
+ self._callback('onActivityPostStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStopped(self, activity):
+ self._callback('onActivityPostStopped', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPreCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreDestroyed(self, activity):
+ self._callback('onActivityPreDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPrePaused(self, activity):
+ self._callback('onActivityPrePaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreResumed(self, activity):
+ self._callback('onActivityPreResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPreSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStarted(self, activity):
+ self._callback('onActivityPreStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStopped(self, activity):
+ self._callback('onActivityPreStopped', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityResumed(self, activity):
+ self._callback('onActivityResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivitySaveInstanceState(self, activity, outState):
+ self._callback('onActivitySaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStarted(self, activity):
+ self._callback('onActivityStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStopped(self, activity):
+ self._callback('onActivityStopped', activity)
+
+
+def register_activity_lifecycle_callbacks(**callbacks):
+ """Register ActivityLifecycleCallbacks instance
+
+ The callbacks are supplied as keyword arguments corresponding to the
+ Application.ActivityLifecycleCallbacks methods such as
+ onActivityStarted. See the ActivityLifecycleCallbacks documentation
+ for the signature of each method.
+
+ The ActivityLifecycleCallbacks instance is returned so it can be
+ supplied to unregister_activity_lifecycle_callbacks if needed.
+ """
+ instance = ActivityLifecycleCallbacks(callbacks)
+ _lifecycle_callbacks.add(instance)
+
+ # Use the registerActivityLifecycleCallbacks method from the
+ # Activity class if it's available (API 29) since it guarantees the
+ # callbacks will only be run for that activity. Otherwise, fallback
+ # to the method on the Application class (API 14). In practice there
+ # should be no difference since p4a applications only have a single
+ # activity.
+ if hasattr(_activity, 'registerActivityLifecycleCallbacks'):
+ _activity.registerActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.registerActivityLifecycleCallbacks(instance)
+ return instance
+
+
+def unregister_activity_lifecycle_callbacks(instance):
+ """Unregister ActivityLifecycleCallbacks instance"""
+ if hasattr(_activity, 'unregisterActivityLifecycleCallbacks'):
+ _activity.unregisterActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.unregisterActivityLifecycleCallbacks(instance)
+
+ try:
+ _lifecycle_callbacks.remove(instance)
+ except KeyError:
+ pass
diff --git a/p4a/pythonforandroid/recipes/android/src/android/broadcast.py b/p4a/pythonforandroid/recipes/android/src/android/broadcast.py
index cb34cd9..3232d83 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/broadcast.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/broadcast.py
@@ -2,7 +2,7 @@
# Broadcast receiver bridge
from jnius import autoclass, PythonJavaClass, java_method
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
class BroadcastReceiver(object):
@@ -20,7 +20,7 @@ class BroadcastReceiver(object):
self.callback(context, intent)
def __init__(self, callback, actions=None, categories=None):
- super(BroadcastReceiver, self).__init__()
+ super().__init__()
self.callback = callback
if not actions and not categories:
@@ -72,7 +72,7 @@ class BroadcastReceiver(object):
def context(self):
from os import environ
if 'PYTHON_SERVICE_ARGUMENT' in environ:
- PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
+ PythonService = autoclass(SERVICE_CLASS_NAME)
return PythonService.mService
- PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
return PythonActivity.mActivity
diff --git a/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py b/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py
index 1dc1b67..a18162e 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/loadingscreen.py
@@ -1,7 +1,9 @@
from jnius import autoclass
+from android.config import ACTIVITY_CLASS_NAME
+
def hide_loading_screen():
- python_activity = autoclass('org.kivy.android.PythonActivity')
- python_activity.removeLoadingScreen()
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.removeLoadingScreen()
diff --git a/p4a/pythonforandroid/recipes/android/src/android/mixer.py b/p4a/pythonforandroid/recipes/android/src/android/mixer.py
index 334f696..303a953 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/mixer.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/mixer.py
@@ -93,7 +93,9 @@ def find_channel(force=False):
if not force:
return None
- return min(busy, key=lambda x: x.play_time)
+ busy.sort(key=lambda x: x.play_time)
+
+ return busy[0]
class ChannelImpl(object):
diff --git a/p4a/pythonforandroid/recipes/android/src/android/permissions.py b/p4a/pythonforandroid/recipes/android/src/android/permissions.py
index 6c2d384..0ce568f 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/permissions.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/permissions.py
@@ -1,6 +1,7 @@
+import threading
try:
- from jnius import autoclass
+ from jnius import autoclass, PythonJavaClass, java_method
except ImportError:
# To allow importing by build/manifest-creating code without
# pyjnius being present:
@@ -8,9 +9,14 @@ except ImportError:
raise RuntimeError("pyjnius not available")
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
+
+
class Permission:
ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
+ ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
+ ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"
ACCESS_LOCATION_EXTRA_COMMANDS = (
"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
)
@@ -92,6 +98,15 @@ class Permission:
BLUETOOTH = (
"android.permission.BLUETOOTH"
)
+ BLUETOOTH_ADVERTISE = (
+ "android.permission.BLUETOOTH_ADVERTISE"
+ )
+ BLUETOOTH_CONNECT = (
+ "android.permission.BLUETOOTH_CONNECT"
+ )
+ BLUETOOTH_SCAN = (
+ "android.permission.BLUETOOTH_SCAN"
+ )
BLUETOOTH_ADMIN = (
"android.permission.BLUETOOTH_ADMIN"
)
@@ -227,6 +242,9 @@ class Permission:
MOUNT_UNMOUNT_FILESYSTEMS = (
"android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
)
+ NEARBY_WIFI_DEVICES = (
+ "android.permission.NEARBY_WIFI_DEVICES"
+ )
NFC = (
"android.permission.NFC"
)
@@ -239,6 +257,9 @@ class Permission:
PERSISTENT_ACTIVITY = (
"android.permission.PERSISTENT_ACTIVITY"
)
+ POST_NOTIFICATIONS = (
+ "android.permission.POST_NOTIFICATIONS"
+ )
PROCESS_OUTGOING_CALLS = (
"android.permission.PROCESS_OUTGOING_CALLS"
)
@@ -263,6 +284,15 @@ class Permission:
READ_LOGS = (
"android.permission.READ_LOGS"
)
+ READ_MEDIA_AUDIO = (
+ "android.permission.READ_MEDIA_AUDIO"
+ )
+ READ_MEDIA_IMAGES = (
+ "android.permission.READ_MEDIA_IMAGES"
+ )
+ READ_MEDIA_VIDEO = (
+ "android.permission.READ_MEDIA_VIDEO"
+ )
READ_PHONE_NUMBERS = (
"android.permission.READ_PHONE_NUMBERS"
)
@@ -421,18 +451,168 @@ class Permission:
)
-def request_permissions(permissions):
- python_activity = autoclass('org.kivy.android.PythonActivity')
- python_activity.requestPermissions(permissions)
+PERMISSION_GRANTED = 0
+PERMISSION_DENIED = -1
-def request_permission(permission):
- request_permissions([permission])
+class _onRequestPermissionsCallback(PythonJavaClass):
+ """Callback class for registering a Python callback from
+ onRequestPermissionsResult in PythonActivity.
+ """
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$PermissionsCallback']
+ __javacontext__ = 'app'
+
+ def __init__(self, func):
+ self.func = func
+ super().__init__()
+
+ @java_method('(I[Ljava/lang/String;[I)V')
+ def onRequestPermissionsResult(self, requestCode,
+ permissions, grantResults):
+ self.func(requestCode, permissions, grantResults)
+
+
+class _RequestPermissionsManager:
+ """Internal class for requesting Android permissions.
+
+ Permissions are requested through the method 'request_permissions' which
+ accepts a list of permissions and an optional callback.
+
+ Any callback will asynchronously receive arguments from
+ onRequestPermissionsResult on PythonActivity after requestPermissions is
+ called.
+
+ The callback supplied must accept two arguments: 'permissions' and
+ 'grantResults' (as supplied to onPermissionsCallbackResult).
+
+ Note that for SDK_INT < 23, run-time permissions are not required, and so
+ the callback will be called immediately.
+
+ The attribute '_java_callback' is initially None, but is set when the first
+ permissions request is made. It is set to an instance of
+ onRequestPermissionsCallback, which allows the Java callback to be
+ propagated to the class method 'python_callback'. This is then, in turn,
+ used to call an application callback if provided to request_permissions.
+
+ The attribute '_callback_id' is incremented with each call to
+ request_permissions which has a callback (the value '1' is used for any
+ call which does not pass a callback). This is passed to requestCode in
+ the Java call, and used to identify (via the _callbacks dictionary)
+ the matching call.
+ """
+ _SDK_INT = None
+ _java_callback = None
+ _callbacks = {1: None}
+ _callback_id = 1
+ # Lock to prevent multiple calls to request_permissions being handled
+ # simultaneously (as incrementing _callback_id is not atomic)
+ _lock = threading.Lock()
+
+ @classmethod
+ def register_callback(cls):
+ """Register Java callback for requestPermissions."""
+ cls._java_callback = _onRequestPermissionsCallback(cls.python_callback)
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.addPermissionsCallback(cls._java_callback)
+
+ @classmethod
+ def request_permissions(cls, permissions, callback=None):
+ """Requests Android permissions from PythonActivity.
+ If 'callback' is supplied, the request is made with a new requestCode
+ and the callback is stored in the _callbacks dict. When a Java callback
+ with the matching requestCode is received, callback will be called
+ with arguments of 'permissions' and 'grant_results'.
+ """
+ if not cls._SDK_INT:
+ # Get the Android build version and store it
+ VERSION = autoclass('android.os.Build$VERSION')
+ cls.SDK_INT = VERSION.SDK_INT
+ if cls.SDK_INT < 23:
+ # No run-time permissions needed, return immediately.
+ if callback:
+ callback(permissions, [True for x in permissions])
+ return
+ # Request permissions
+ with cls._lock:
+ if not cls._java_callback:
+ cls.register_callback()
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if not callback:
+ mActivity.requestPermissions(permissions)
+ else:
+ cls._callback_id += 1
+ mActivity.requestPermissionsWithRequestCode(
+ permissions, cls._callback_id)
+ cls._callbacks[cls._callback_id] = callback
+
+ @classmethod
+ def python_callback(cls, requestCode, permissions, grantResults):
+ """Calls the relevant callback with arguments of 'permissions'
+ and 'grantResults'."""
+ # Convert from Android codes to True/False
+ grant_results = [x == PERMISSION_GRANTED for x in grantResults]
+ if cls._callbacks.get(requestCode):
+ cls._callbacks[requestCode](permissions, grant_results)
+
+
+# Public API methods for requesting permissions
+
+def request_permissions(permissions, callback=None):
+ """Requests Android permissions.
+
+ Args:
+ permissions (str): A list of permissions to requests (str)
+ callback (callable, optional): A function to call when the request
+ is completed (callable)
+
+ Returns:
+ None
+
+ Notes:
+
+ Permission strings can be imported from the 'Permission' class in this
+ module. For example:
+
+ from android import Permission
+ permissions_list = [Permission.CAMERA,
+ Permission.WRITE_EXTERNAL_STORAGE]
+
+ See the p4a source file 'permissions.py' for a list of valid permission
+ strings (pythonforandroid/recipes/android/src/android/permissions.py).
+
+ Any callback supplied must accept two arguments:
+ permissions (list of str): A list of permission strings
+ grant_results (list of bool): A list of bools indicating whether the
+ respective permission was granted.
+ See Android documentation for onPermissionsCallbackResult for
+ further information.
+
+ Note that if the request is interupted the callback may contain an empty
+ list of permissions, without permissions being granted; the App should
+ check that each permission requested has been granted.
+
+ Also note that when calling request_permission on SDK_INT < 23, the
+ callback will be returned immediately as requesting permissions is not
+ required.
+ """
+ _RequestPermissionsManager.request_permissions(permissions, callback)
+
+
+def request_permission(permission, callback=None):
+ request_permissions([permission], callback)
def check_permission(permission):
- python_activity = autoclass('org.kivy.android.PythonActivity')
- result = bool(python_activity.checkCurrentPermission(
+ """Checks if an app holds the passed permission.
+
+ Args:
+ - permission An Android permission (str)
+
+ Returns:
+ bool: True if the app holds the permission given, False otherwise.
+ """
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ result = bool(mActivity.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 8d2d116..b20f6cc 100644
--- a/p4a/pythonforandroid/recipes/android/src/android/runnable.py
+++ b/p4a/pythonforandroid/recipes/android/src/android/runnable.py
@@ -1,14 +1,17 @@
'''
Runnable
========
-
'''
from jnius import PythonJavaClass, java_method, autoclass
-from android.config import JAVA_NAMESPACE
+from android.config import ACTIVITY_CLASS_NAME
-# reference to the activity
-_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+# Reference to the activity
+_PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
+
+# Cache of functions table. In older Android versions the number of JNI references
+# is limited, so by caching them we avoid running out.
+__functionstable__ = {}
class Runnable(PythonJavaClass):
@@ -20,7 +23,7 @@ class Runnable(PythonJavaClass):
__runnables__ = []
def __init__(self, func):
- super(Runnable, self).__init__()
+ super().__init__()
self.func = func
def __call__(self, *args, **kwargs):
@@ -44,6 +47,12 @@ 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.
'''
+ if f not in __functionstable__:
+ rfunction = Runnable(f) # store the runnable function
+ __functionstable__[f] = {"rfunction": rfunction}
+ rfunction = __functionstable__[f]["rfunction"]
+
def f2(*args, **kwargs):
- Runnable(f)(*args, **kwargs)
+ rfunction(*args, **kwargs)
+
return f2
diff --git a/p4a/pythonforandroid/recipes/android/src/android/storage.py b/p4a/pythonforandroid/recipes/android/src/android/storage.py
new file mode 100644
index 0000000..aa6d781
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/android/src/android/storage.py
@@ -0,0 +1,117 @@
+from jnius import autoclass, cast
+import os
+
+from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
+
+
+Environment = autoclass('android.os.Environment')
+File = autoclass('java.io.File')
+
+
+def _android_has_is_removable_func():
+ VERSION = autoclass('android.os.Build$VERSION')
+ return (VERSION.SDK_INT >= 24)
+
+
+def _get_sdcard_path():
+ """ Internal function to return getExternalStorageDirectory()
+ path. This is internal because it may either return the internal,
+ or an external sd card, depending on the device.
+ Use primary_external_storage_path()
+ or secondary_external_storage_path() instead which try to
+ distinguish this properly.
+ """
+ return (
+ Environment.getExternalStorageDirectory().getAbsolutePath()
+ )
+
+
+def _get_activity():
+ """
+ Retrieves the activity from `PythonActivity` fallback to `PythonService`.
+ """
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
+ activity = PythonActivity.mActivity
+ if activity is None:
+ # assume we're running from the background service
+ PythonService = autoclass(SERVICE_CLASS_NAME)
+ activity = PythonService.mService
+ return activity
+
+
+def app_storage_path():
+ """ Locate the built-in device storage used for this app only.
+
+ This storage is APP-SPECIFIC, and not visible to other apps.
+ It will be wiped when your app is uninstalled.
+
+ Returns directory path to storage.
+ """
+ activity = _get_activity()
+ currentActivity = cast('android.app.Activity', activity)
+ context = cast('android.content.ContextWrapper',
+ currentActivity.getApplicationContext())
+ file_p = cast('java.io.File', context.getFilesDir())
+ return os.path.normpath(os.path.abspath(
+ file_p.getAbsolutePath().replace("/", os.path.sep)))
+
+
+def primary_external_storage_path():
+ """ Locate the built-in device storage that user can see via file browser.
+ Often found at: /sdcard/
+
+ This is storage is SHARED, and visible to other apps and the user.
+ It will remain untouched when your app is uninstalled.
+
+ Returns directory path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ """
+ if _android_has_is_removable_func():
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if not Environment.isExternalStorageRemovable(File(sdpath)):
+ return sdpath
+ if "EXTERNAL_STORAGE" in os.environ:
+ return os.environ["EXTERNAL_STORAGE"]
+ raise RuntimeError(
+ "unexpectedly failed to determine " +
+ "primary external storage path"
+ )
+
+
+def secondary_external_storage_path():
+ """ Locate the external SD Card storage, which may not be present.
+ Often found at: /sdcard/External_SD/
+
+ This storage is SHARED, visible to other apps, and may not be
+ be available if the user didn't put in an external SD card.
+ It will remain untouched when your app is uninstalled.
+
+ Returns None if not found, otherwise path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ If it is not writable and presents as empty even with
+ permissions, then the external sd card may not be present.
+ """
+ if _android_has_is_removable_func:
+ # See if getExternalStorageDirectory() returns secondary ext storage:
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if Environment.isExternalStorageRemovable(File(sdpath)):
+ if os.path.exists(sdpath):
+ return sdpath
+
+ # See if we can take a guess based on environment variables:
+ p = None
+ if "SECONDARY_STORAGE" in os.environ:
+ p = os.environ["SECONDARY_STORAGE"]
+ elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
+ p = os.environ["EXTERNAL_SDCARD_STORAGE"]
+ if p is not None and os.path.exists(p):
+ return p
+ return None
diff --git a/p4a/pythonforandroid/recipes/android/src/setup.py b/p4a/pythonforandroid/recipes/android/src/setup.py
index 2e95a86..bcd411f 100755
--- a/p4a/pythonforandroid/recipes/android/src/setup.py
+++ b/p4a/pythonforandroid/recipes/android/src/setup.py
@@ -3,15 +3,9 @@ import os
library_dirs = ['libs/' + os.environ['ARCH']]
lib_dict = {
- 'pygame': ['sdl'],
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
}
-sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], [])
-
-renpy_sound = Extension('android._android_sound',
- ['android/_android_sound.c', 'android/_android_sound_jni.c', ],
- libraries=sdl_libs + ['log'],
- library_dirs=library_dirs)
+sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])
modules = [Extension('android._android',
['android/_android.c', 'android/_android_jni.c'],
@@ -22,10 +16,6 @@ modules = [Extension('android._android',
libraries=['log'],
library_dirs=library_dirs)]
-if int(os.environ['IS_PYGAME']):
- modules.append(renpy_sound)
-
-
setup(name='android',
version='1.0',
packages=['android'],
diff --git a/p4a/pythonforandroid/recipes/apsw/__init__.py b/p4a/pythonforandroid/recipes/apsw/__init__.py
index 6098e4b..42ad3ba 100644
--- a/p4a/pythonforandroid/recipes/apsw/__init__.py
+++ b/p4a/pythonforandroid/recipes/apsw/__init__.py
@@ -6,7 +6,7 @@ import sh
class ApswRecipe(PythonRecipe):
version = '3.15.0-r1'
url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
- depends = ['sqlite3', ('python2', 'python3'), 'setuptools']
+ depends = ['sqlite3', 'setuptools']
call_hostpython_via_targetpython = False
site_packages_name = 'apsw'
@@ -20,10 +20,10 @@ class ApswRecipe(PythonRecipe):
'build_ext',
'--enable=fts4', _env=env)
# Install python bindings
- super(ApswRecipe, self).build_arch(arch)
+ super().build_arch(arch)
def get_recipe_env(self, arch):
- env = super(ApswRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
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)
diff --git a/p4a/pythonforandroid/recipes/argon2-cffi/__init__.py b/p4a/pythonforandroid/recipes/argon2-cffi/__init__.py
new file mode 100644
index 0000000..0450d78
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/argon2-cffi/__init__.py
@@ -0,0 +1,17 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class Argon2Recipe(CompiledComponentsPythonRecipe):
+ version = '20.1.0'
+ url = 'git+https://github.com/hynek/argon2-cffi'
+ depends = ['setuptools', 'cffi']
+ call_hostpython_via_targetpython = False
+ build_cmd = 'build'
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['ARGON2_CFFI_USE_SSE2'] = '0'
+ return env
+
+
+recipe = Argon2Recipe()
diff --git a/p4a/pythonforandroid/recipes/audiostream/__init__.py b/p4a/pythonforandroid/recipes/audiostream/__init__.py
index 4197abd..00c92b3 100644
--- a/p4a/pythonforandroid/recipes/audiostream/__init__.py
+++ b/p4a/pythonforandroid/recipes/audiostream/__init__.py
@@ -1,32 +1,51 @@
from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import shprint, current_directory, info
+import sh
from os.path import join
class AudiostreamRecipe(CythonRecipe):
- version = 'master'
+ # audiostream has no tagged versions; this is the latest commit to master 2020-12-22
+ # it includes a fix for the dyload issue on android that was preventing use
+ version = '69f6b100f1ea4e3982a1acf6bbb0804e31a2cd50'
url = 'https://github.com/kivy/audiostream/archive/{version}.zip'
+ sha256sum = '4d415c91706fd76865d0d22f1945f87900dc42125ff5a6c8d77898ccdf613c21'
name = 'audiostream'
- depends = [('python2', 'python3'), ('sdl', 'sdl2'), 'pyjnius']
+ depends = ['python3', '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 = 'SDL2'
- sdl_mixer_include = 'SDL2_mixer'
- env['USE_SDL2'] = 'True'
- env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')
+ env = super().get_recipe_env(arch)
+ sdl_include = 'SDL2'
- env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format(
+ env['USE_SDL2'] = 'True'
+ env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')
+
+ env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include'.format(
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
+ sdl_include=sdl_include)
+
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):
+ env['CFLAGS'] += ' -I{include_dir}'.format(include_dir=include_dir)
+
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
env['LIBLINK'] = 'NOTNONE' # Hacky fix. Needed by audiostream setup.py
return env
+ def postbuild_arch(self, arch):
+ # TODO: This code was copied from pyjnius, but judging by the
+ # audiostream history, it looks like this step might have
+ # happened automatically in the past.
+ # Given the goal of migrating off of recipes, it would
+ # be good to repair or build infrastructure for doing this
+ # automatically, for when including a java class is
+ # the best solution to a problem.
+ super().postbuild_arch(arch)
+ info('Copying audiostream java files to classes build dir')
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(sh.cp, '-a', join('audiostream', 'platform', 'android'), self.ctx.javaclass_dir)
+
recipe = AudiostreamRecipe()
diff --git a/p4a/pythonforandroid/recipes/bcrypt/__init__.py b/p4a/pythonforandroid/recipes/bcrypt/__init__.py
new file mode 100644
index 0000000..da220ff
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/bcrypt/__init__.py
@@ -0,0 +1,22 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+
+
+class BCryptRecipe(CompiledComponentsPythonRecipe):
+ name = 'bcrypt'
+ version = '3.1.7'
+ url = 'https://github.com/pyca/bcrypt/archive/{version}.tar.gz'
+ depends = ['openssl', 'cffi']
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ 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 = BCryptRecipe()
diff --git a/p4a/pythonforandroid/recipes/boost/__init__.py b/p4a/pythonforandroid/recipes/boost/__init__.py
index 53d9388..aa386c9 100644
--- a/p4a/pythonforandroid/recipes/boost/__init__.py
+++ b/p4a/pythonforandroid/recipes/boost/__init__.py
@@ -1,10 +1,13 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
+from pythonforandroid.util import current_directory
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
from os.path import join, exists
from os import environ
+import shutil
import sh
"""
-This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build
+This recipe bootstraps Boost from source to build Boost.Build
including python bindings
"""
@@ -12,7 +15,8 @@ including python bindings
class BoostRecipe(Recipe):
# Todo: make recipe compatible with all p4a architectures
'''
- .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
+ .. note:: This recipe can be built only against API 21+ and an android
+ ndk >= r19
.. versionchanged:: 0.6.0
Rewrote recipe to support clang's build. The following changes has
@@ -24,14 +28,24 @@ class BoostRecipe(Recipe):
- 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``
+
+ .. versionchanged:: 2019.08.09.1.dev0
+
+ - Bumped version number to 1.68.0
+ - Adapted to work with ndk-r19+
'''
- 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']
+ version = '1.69.0'
+ url = (
+ 'https://downloads.sourceforge.net/project/boost/'
+ 'boost/{version}/boost_{version_underscore}.tar.bz2'
+ )
+ depends = ['python3']
+ patches = [
+ 'disable-so-version.patch',
+ 'use-android-libs.patch',
+ 'fix-android-issues.patch',
+ ]
+ need_stl_shared = True
@property
def versioned_url(self):
@@ -39,65 +53,51 @@ class BoostRecipe(Recipe):
return None
return self.url.format(
version=self.version,
- version_underscore=self.version.replace('.', '_'))
+ version_underscore=self.version.replace('.', '_'),
+ )
def should_build(self, arch):
return not exists(join(self.get_build_dir(arch.arch), 'b2'))
def prebuild_arch(self, arch):
- super(BoostRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
- if not exists(env['CROSSHOME']):
- # Make custom toolchain
- bash = sh.Command('bash')
- 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'] + '-' + 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'))
+ shutil.copyfile(
+ join(self.get_recipe_dir(), 'user-config.jam'),
+ join(env['BOOST_BUILD_PATH'], 'user-config.jam'),
+ )
def build_arch(self, arch):
- super(BoostRecipe, self).build_arch(arch)
+ super().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') # Do not pass env
- # Install app stl
- 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', '')
+ if not exists('b2'):
+ # Compile Boost.Build engine with this custom toolchain
+ bash = sh.Command('bash')
+ shprint(bash, 'bootstrap.sh') # Do not pass env
def get_recipe_env(self, 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
+ # find user-config.jam
+ env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch)
+ # find boost source
+ env['BOOST_ROOT'] = env['BOOST_BUILD_PATH']
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['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.link_version
- env['ARCH'] = self.select_build_arch(arch)
- env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
- env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
+ env['ARCH'] = arch.arch.replace('-', '')
+ env['TARGET_TRIPLET'] = arch.target
+ env['CROSSHOST'] = arch.command_prefix
+ env['CROSSHOME'] = self.ctx.ndk.llvm_prebuilt_dir
return env
diff --git a/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch b/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch
index 5413480..40bdea4 100644
--- a/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch
+++ b/p4a/pythonforandroid/recipes/boost/fix-android-issues.patch
@@ -1,10 +1,26 @@
-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
+diff -u -r boost_1_69_0.orig/boost/asio/detail/config.hpp boost_1_69_0/boost/asio/detail/config.hpp
+--- boost_1_69_0.orig/boost/asio/detail/config.hpp 2018-12-05 20:58:15.000000000 +0100
++++ boost_1_69_0/boost/asio/detail/config.hpp 2018-12-13 14:52:06.000000000 +0100
+@@ -815,7 +815,11 @@
+ # if (_LIBCPP_VERSION < 7000)
+ # 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 // (_LIBCPP_VERSION < 7000)
+diff -u -r boost_1_69_0.orig/boost/config/user.hpp boost_1_69_0/boost/config/user.hpp
+--- boost_1_69_0.orig/boost/config/user.hpp 2018-12-05 20:58:16.000000000 +0100
++++ boost_1_69_0/boost/config/user.hpp 2018-12-13 14:35:29.000000000 +0100
@@ -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:
@@ -13,41 +29,25 @@ diff -u -r boost_1_68_0.orig/boost/config/user.hpp boost_1_68_0/boost/config/use
+
// 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
+
+diff -u -r boost_1_69_0.orig/boost/system/error_code.hpp boost_1_69_0/boost/system/error_code.hpp
+--- boost_1_69_0.orig/boost/system/error_code.hpp 2018-12-05 20:58:23.000000000 +0100
++++ boost_1_69_0/boost/system/error_code.hpp 2018-12-13 14:53:33.000000000 +0100
+@@ -14,6 +14,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
+ #include
+diff -u -r boost_1_69_0.orig/libs/filesystem/src/operations.cpp boost_1_69_0/libs/filesystem/src/operations.cpp
+--- boost_1_69_0.orig/libs/filesystem/src/operations.cpp 2018-12-05 20:58:17.000000000 +0100
++++ boost_1_69_0/libs/filesystem/src/operations.cpp 2018-12-13 14:55:41.000000000 +0100
@@ -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
@@ -64,5 +64,23 @@ diff -u -r boost_1_68_0.orig/libs/filesystem/src/operations.cpp boost_1_68_0/lib
+# endif
+
typedef int err_t;
-
+
// POSIX uses a 0 return to indicate success
+diff -u -r boost_1_69_0.orig/tools/build/src/tools/common.jam boost_1_69_0/tools/build/src/tools/common.jam
+--- boost_1_69_0.orig/tools/build/src/tools/common.jam 2019-01-25 23:18:34.544755629 +0200
++++ boost_1_69_0/tools/build/src/tools/common.jam 2019-01-25 23:20:42.309047754 +0200
+@@ -976,10 +976,10 @@
+ }
+
+ # Ditto, from Clang 4
+- if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]
+- {
+- version = $(version[1]) ;
+- }
++ #if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]
++ #{
++ # version = $(version[1]) ;
++ #}
+
+ # On intel, version is not added, because it does not matter and it is the
+ # version of vc used as backend that matters. Ideally, we should encode the
diff --git a/p4a/pythonforandroid/recipes/boost/user-config.jam b/p4a/pythonforandroid/recipes/boost/user-config.jam
index e50b50a..fa1eef1 100644
--- a/p4a/pythonforandroid/recipes/boost/user-config.jam
+++ b/p4a/pythonforandroid/recipes/boost/user-config.jam
@@ -1,6 +1,7 @@
import os ;
local ARCH = [ os.environ ARCH ] ;
+local TARGET_TRIPLET = [ os.environ TARGET_TRIPLET ] ;
local CROSSHOME = [ os.environ CROSSHOME ] ;
local PYTHON_HOST = [ os.environ PYTHON_HOST ] ;
local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
@@ -8,42 +9,22 @@ 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 clang : $(ARCH) : $(CROSSHOME)/bin/arm-linux-androideabi-clang++ :
-$(CROSSHOME)/bin/arm-linux-androideabi-ar
-$(CROSSHOME)/sysroot
-$(ARCH)
--fexceptions
--frtti
--fpic
+using clang : $(ARCH) : $(CROSSHOME)/bin/$(TARGET_TRIPLET)-clang++ :
+$(CROSSHOME)/bin/llvm-ar
+-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
+-funwind-tables
+-fstack-protector-strong
+-no-canonical-prefixes
+-Wformat
+-Werror=format-security
+-frtti
+-fexceptions
+-DNDEBUG
+-g
+-Oz
+-mthumb
-Wl,-z,relro
-Wl,-z,now
-lc++_shared
diff --git a/p4a/pythonforandroid/recipes/cdecimal/__init__.py b/p4a/pythonforandroid/recipes/cdecimal/__init__.py
index 94929c7..a444eb1 100644
--- a/p4a/pythonforandroid/recipes/cdecimal/__init__.py
+++ b/p4a/pythonforandroid/recipes/cdecimal/__init__.py
@@ -13,7 +13,7 @@ class CdecimalRecipe(CompiledComponentsPythonRecipe):
'cross-compile.patch']
def prebuild_arch(self, arch):
- super(CdecimalRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
if not is_darwin():
if '64' in arch.arch:
machine = 'ansi64'
diff --git a/p4a/pythonforandroid/recipes/cffi/__init__.py b/p4a/pythonforandroid/recipes/cffi/__init__.py
index 50458e5..a198a3d 100644
--- a/p4a/pythonforandroid/recipes/cffi/__init__.py
+++ b/p4a/pythonforandroid/recipes/cffi/__init__.py
@@ -7,7 +7,7 @@ class CffiRecipe(CompiledComponentsPythonRecipe):
Extra system dependencies: autoconf, automake and libtool.
"""
name = 'cffi'
- version = '1.11.5'
+ version = '1.13.2'
url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
depends = ['setuptools', 'pycparser', 'libffi']
@@ -19,14 +19,14 @@ class CffiRecipe(CompiledComponentsPythonRecipe):
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)
+ env = super().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)
+ env = super().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)
@@ -35,18 +35,13 @@ class CffiRecipe(CompiledComponentsPythonRecipe):
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['LDFLAGS'] += ' -L{}'.format(arch.ndk_lib_dir_versioned)
env['PYTHONPATH'] = ':'.join([
- self.ctx.get_site_packages_dir(),
+ self.ctx.get_site_packages_dir(arch),
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'
+ env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.link_version)
return env
diff --git a/p4a/pythonforandroid/recipes/cherrypy/__init__.py b/p4a/pythonforandroid/recipes/cherrypy/__init__.py
deleted file mode 100644
index 6d3844b..0000000
--- a/p4a/pythonforandroid/recipes/cherrypy/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class CherryPyRecipe(PythonRecipe):
- version = '5.1.0'
- url = 'https://bitbucket.org/cherrypy/cherrypy/get/{version}.tar.gz'
- 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 95f08f1..2ee2d05 100644
--- a/p4a/pythonforandroid/recipes/coverage/__init__.py
+++ b/p4a/pythonforandroid/recipes/coverage/__init__.py
@@ -7,7 +7,7 @@ class CoverageRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz'
- depends = [('hostpython2', 'hostpython3'), 'setuptools']
+ depends = ['hostpython3', 'setuptools']
patches = ['fallback-utf8.patch']
diff --git a/p4a/pythonforandroid/recipes/cppy/__init__.py b/p4a/pythonforandroid/recipes/cppy/__init__.py
new file mode 100644
index 0000000..f61e2c2
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/cppy/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class CppyRecipe(PythonRecipe):
+ site_packages_name = 'cppy'
+ version = '1.1.0'
+ url = 'https://github.com/nucleic/cppy/archive/{version}.zip'
+ call_hostpython_via_targetpython = False
+ # to be detected by the matplotlib install script
+ install_in_hostpython = True
+ depends = ['setuptools']
+
+
+recipe = CppyRecipe()
diff --git a/p4a/pythonforandroid/recipes/cryptography/__init__.py b/p4a/pythonforandroid/recipes/cryptography/__init__.py
index 1b7baba..182c745 100644
--- a/p4a/pythonforandroid/recipes/cryptography/__init__.py
+++ b/p4a/pythonforandroid/recipes/cryptography/__init__.py
@@ -3,14 +3,13 @@ from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
class CryptographyRecipe(CompiledComponentsPythonRecipe):
name = 'cryptography'
- version = '2.6.1'
+ version = '2.8'
url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
- depends = ['openssl', 'idna', 'asn1crypto', 'six', 'setuptools',
- 'enum34', 'ipaddress', 'cffi']
+ depends = ['openssl', 'six', 'setuptools', 'cffi']
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch):
- env = super(CryptographyRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
env['CFLAGS'] += openssl_recipe.include_flags(arch)
diff --git a/p4a/pythonforandroid/recipes/cymunk/__init__.py b/p4a/pythonforandroid/recipes/cymunk/__init__.py
index 96d4169..272c18f 100644
--- a/p4a/pythonforandroid/recipes/cymunk/__init__.py
+++ b/p4a/pythonforandroid/recipes/cymunk/__init__.py
@@ -6,7 +6,5 @@ class CymunkRecipe(CythonRecipe):
url = 'https://github.com/tito/cymunk/archive/{version}.zip'
name = 'cymunk'
- depends = [('python2', 'python3crystax', 'python3')]
-
recipe = CymunkRecipe()
diff --git a/p4a/pythonforandroid/recipes/cython/__init__.py b/p4a/pythonforandroid/recipes/cython/__init__.py
new file mode 100644
index 0000000..9135e18
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/cython/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class CythonRecipe(CompiledComponentsPythonRecipe):
+
+ version = '0.29.28'
+ url = 'https://github.com/cython/cython/archive/{version}.tar.gz'
+ site_packages_name = 'cython'
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+
+recipe = CythonRecipe()
diff --git a/p4a/pythonforandroid/recipes/enum34/__init__.py b/p4a/pythonforandroid/recipes/enum34/__init__.py
deleted file mode 100644
index 9a438c5..0000000
--- a/p4a/pythonforandroid/recipes/enum34/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class Enum34Recipe(PythonRecipe):
- version = '1.1.6'
- url = 'https://pypi.python.org/packages/source/e/enum34/enum34-{version}.tar.gz'
- 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/evdev/__init__.py b/p4a/pythonforandroid/recipes/evdev/__init__.py
index afd542e..b69169d 100644
--- a/p4a/pythonforandroid/recipes/evdev/__init__.py
+++ b/p4a/pythonforandroid/recipes/evdev/__init__.py
@@ -5,6 +5,7 @@ class EvdevRecipe(CompiledComponentsPythonRecipe):
name = 'evdev'
version = 'v0.4.7'
url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip'
+ call_hostpython_via_targetpython = False
depends = []
@@ -17,8 +18,8 @@ class EvdevRecipe(CompiledComponentsPythonRecipe):
'evdev-permissions.patch']
def get_recipe_env(self, arch=None):
- env = super(EvdevRecipe, self).get_recipe_env(arch)
- env['NDKPLATFORM'] = self.ctx.ndk_platform
+ env = super().get_recipe_env(arch)
+ env['SYSROOT'] = self.ctx.ndk.sysroot
return env
diff --git a/p4a/pythonforandroid/recipes/evdev/include-dir.patch b/p4a/pythonforandroid/recipes/evdev/include-dir.patch
index d6a7c81..a1c41e7 100644
--- a/p4a/pythonforandroid/recipes/evdev/include-dir.patch
+++ b/p4a/pythonforandroid/recipes/evdev/include-dir.patch
@@ -6,7 +6,7 @@ diff -Naur orig/setup.py v0.4.7/setup.py
#-----------------------------------------------------------------------------
def create_ecodes():
- header = '/usr/include/linux/input.h'
-+ header = os.environ['NDKPLATFORM'] + '/usr/include/linux/input.h'
++ header = os.environ['SYSROOT'] + '/usr/include/linux/input.h'
if not os.path.isfile(header):
msg = '''\
diff --git a/p4a/pythonforandroid/recipes/ffmpeg/__init__.py b/p4a/pythonforandroid/recipes/ffmpeg/__init__.py
index f8e3ec1..9414552 100644
--- a/p4a/pythonforandroid/recipes/ffmpeg/__init__.py
+++ b/p4a/pythonforandroid/recipes/ffmpeg/__init__.py
@@ -4,8 +4,9 @@ import sh
class FFMpegRecipe(Recipe):
- version = '3.4.5'
- url = 'http://ffmpeg.org/releases/ffmpeg-{version}.tar.bz2'
+ version = 'n4.3.1'
+ # Moved to github.com instead of ffmpeg.org to improve download speed
+ url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip'
depends = ['sdl2'] # Need this to build correct recipe order
opts_depends = ['openssl', 'ffpyplayer_codecs']
patches = ['patches/configure.patch']
@@ -18,7 +19,7 @@ class FFMpegRecipe(Recipe):
self.apply_patches(arch)
def get_recipe_env(self, arch):
- env = super(FFMpegRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['NDK'] = self.ctx.ndk_dir
return env
@@ -36,14 +37,20 @@ class FFMpegRecipe(Recipe):
'--enable-nonfree',
'--enable-protocol=https,tls_openssl',
]
- build_dir = Recipe.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
- cflags += ['-I' + build_dir + '/include/']
+ build_dir = Recipe.get_recipe(
+ 'openssl', self.ctx).get_build_dir(arch.arch)
+ cflags += ['-I' + build_dir + '/include/',
+ '-DOPENSSL_API_COMPAT=0x10002000L']
ldflags += ['-L' + build_dir]
if 'ffpyplayer_codecs' in self.ctx.recipe_build_order:
+ # Enable GPL
+ flags += ['--enable-gpl']
+
# libx264
flags += ['--enable-libx264']
- build_dir = Recipe.get_recipe('libx264', self.ctx).get_build_dir(arch.arch)
+ build_dir = Recipe.get_recipe(
+ 'libx264', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lx264', '-L' + build_dir + '/lib/']
@@ -52,6 +59,14 @@ class FFMpegRecipe(Recipe):
build_dir = Recipe.get_recipe('libshine', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lshine', '-L' + build_dir + '/lib/']
+ ldflags += ['-lm']
+
+ # libvpx
+ flags += ['--enable-libvpx']
+ build_dir = Recipe.get_recipe(
+ 'libvpx', self.ctx).get_build_dir(arch.arch)
+ cflags += ['-I' + build_dir + '/include/']
+ ldflags += ['-lvpx', '-L' + build_dir + '/lib/']
# Enable all codecs:
flags += [
@@ -67,7 +82,7 @@ class FFMpegRecipe(Recipe):
'--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',
+ '--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1,rtsp',
]
# needed to prevent _ffmpeg.so: version node not found for symbol av_init_packet@LIBAVFORMAT_52
@@ -78,39 +93,48 @@ class FFMpegRecipe(Recipe):
# disable binaries / doc
flags += [
- '--disable-ffmpeg',
- '--disable-ffplay',
- '--disable-ffprobe',
- '--disable-ffserver',
+ '--disable-programs',
'--disable-doc',
]
# other flags:
flags += [
'--enable-filter=aresample,resample,crop,adelay,volume,scale',
- '--enable-protocol=file,http',
+ '--enable-protocol=file,http,hls,udp,tcp',
'--enable-small',
'--enable-hwaccels',
- '--enable-gpl',
'--enable-pic',
'--disable-static',
+ '--disable-debug',
'--enable-shared',
]
+ if 'arm64' in arch.arch:
+ arch_flag = 'aarch64'
+ elif 'x86' in arch.arch:
+ arch_flag = 'x86'
+ flags += ['--disable-asm']
+ else:
+ arch_flag = 'arm'
+
# android:
flags += [
'--target-os=android',
- '--cross-prefix=arm-linux-androideabi-',
- '--arch=arm',
- '--sysroot=' + self.ctx.ndk_platform,
+ '--enable-cross-compile',
+ '--cross-prefix={}-'.format(arch.target),
+ '--arch={}'.format(arch_flag),
+ '--strip={}'.format(self.ctx.ndk.llvm_strip),
+ '--sysroot={}'.format(self.ctx.ndk.sysroot),
'--enable-neon',
'--prefix={}'.format(realpath('.')),
]
- cflags += [
- '-mfpu=vfpv3-d16',
- '-mfloat-abi=softfp',
- '-fPIC',
- ]
+
+ if arch_flag == 'arm':
+ cflags += [
+ '-mfpu=vfpv3-d16',
+ '-mfloat-abi=softfp',
+ '-fPIC',
+ ]
env['CFLAGS'] += ' ' + ' '.join(cflags)
env['LDFLAGS'] += ' ' + ' '.join(ldflags)
@@ -120,7 +144,8 @@ class FFMpegRecipe(Recipe):
shprint(sh.make, '-j4', _env=env)
shprint(sh.make, 'install', _env=env)
# copy libs:
- sh.cp('-a', sh.glob('./lib/lib*.so'), self.ctx.get_libs_dir(arch.arch))
+ 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
index b898c7f..cacf029 100644
--- a/p4a/pythonforandroid/recipes/ffmpeg/patches/configure.patch
+++ b/p4a/pythonforandroid/recipes/ffmpeg/patches/configure.patch
@@ -1,40 +1,11 @@
---- ./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 @@
+--- ./configure 2020-10-11 19:12:16.759760904 +0200
++++ ./configure.patch 2020-10-11 19:15:49.059533563 +0200
+@@ -6361,7 +6361,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 librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++"
-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 ||
++enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine -lm
+ enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
+ require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
+ enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++
\ No newline at end of file
diff --git a/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py b/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py
index 9ff29b7..6260037 100644
--- a/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py
+++ b/p4a/pythonforandroid/recipes/ffpyplayer/__init__.py
@@ -4,13 +4,13 @@ from os.path import join
class FFPyPlayerRecipe(CythonRecipe):
- version = '6f7568b498715c2da88f061ebad082a042514923'
+ version = 'v4.3.2'
url = 'https://github.com/matham/ffpyplayer/archive/{version}.zip'
- depends = [('python2', 'python3'), 'sdl2', 'ffmpeg']
+ depends = ['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 = super().get_recipe_env(arch)
build_dir = Recipe.get_recipe('ffmpeg', self.ctx).get_build_dir(arch.arch)
env["FFMPEG_INCLUDE_DIR"] = join(build_dir, "include")
@@ -20,7 +20,21 @@ class FFPyPlayerRecipe(CythonRecipe):
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')
+
+ # ffpyplayer does not allow to pass more than one include dir for sdl2_mixer (and ATM is
+ # not needed), so we only pass the first one.
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ env["SDL2_MIXER_INCLUDE_DIR"] = sdl2_mixer_recipe.get_include_dirs(arch)[0]
+
+ # NDKPLATFORM and LIBLINK are our switches for detecting Android platform, so can't be empty
+ # FIXME: We may want to introduce a cleaner approach to this?
+ env['NDKPLATFORM'] = "NOTNONE"
+ env['LIBLINK'] = 'NOTNONE'
+
+ # ffmpeg recipe enables GPL components only if ffpyplayer_codecs recipe used.
+ # Therefor we need to disable libpostproc if skipped.
+ if 'ffpyplayer_codecs' not in self.ctx.recipe_build_order:
+ env["CONFIG_POSTPROC"] = '0'
return env
diff --git a/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py b/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
index b324194..eedb126 100644
--- a/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
+++ b/p4a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
@@ -2,7 +2,7 @@ from pythonforandroid.toolchain import Recipe
class FFPyPlayerCodecsRecipe(Recipe):
- depends = ['libshine', 'libx264']
+ depends = ['libx264', 'libshine', 'libvpx']
def build_arch(self, arch):
pass
diff --git a/p4a/pythonforandroid/recipes/flask/__init__.py b/p4a/pythonforandroid/recipes/flask/__init__.py
index 1a9b685..b272942 100644
--- a/p4a/pythonforandroid/recipes/flask/__init__.py
+++ b/p4a/pythonforandroid/recipes/flask/__init__.py
@@ -3,13 +3,10 @@ from pythonforandroid.recipe import PythonRecipe
class FlaskRecipe(PythonRecipe):
- # 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'
+ version = '2.0.3'
url = 'https://github.com/pallets/flask/archive/{version}.zip'
- depends = [('python2', 'python3', 'python3crystax'), 'setuptools']
+ depends = ['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 8ac01e4..ad959f6 100644
--- a/p4a/pythonforandroid/recipes/fontconfig/__init__.py
+++ b/p4a/pythonforandroid/recipes/fontconfig/__init__.py
@@ -1,3 +1,5 @@
+from os.path import join
+
from pythonforandroid.recipe import BootstrapNDKRecipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
@@ -13,7 +15,13 @@ class FontconfigRecipe(BootstrapNDKRecipe):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", 'fontconfig', _env=env)
+ shprint(
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
+ "V=1",
+ "APP_ALLOW_MISSING_DEPS=true",
+ "fontconfig",
+ _env=env,
+ )
recipe = FontconfigRecipe()
diff --git a/p4a/pythonforandroid/recipes/freetype-py/__init__.py b/p4a/pythonforandroid/recipes/freetype-py/__init__.py
new file mode 100644
index 0000000..7be2f2e
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/freetype-py/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class FreetypePyRecipe(PythonRecipe):
+ version = '2.2.0'
+ url = 'https://github.com/rougier/freetype-py/archive/refs/tags/v{version}.tar.gz'
+ depends = ['freetype']
+ patches = ['fall-back-to-distutils.patch']
+ site_packages_name = 'freetype'
+
+
+recipe = FreetypePyRecipe()
diff --git a/p4a/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch b/p4a/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch
new file mode 100644
index 0000000..0f06f18
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch
@@ -0,0 +1,15 @@
+diff -ruN freetype-py.orig/setup.py freetype-py/setup.py
+--- freetype-py.orig/setup.py 2020-07-09 20:58:51.000000000 +0700
++++ freetype-py/setup.py 2022-03-02 19:28:17.948831134 +0700
+@@ -12,7 +12,10 @@
+ from io import open
+ from os import path
+
+-from setuptools import setup
++try:
++ from setuptools import setup
++except ImportError:
++ from distutils.core import setup
+
+ if os.environ.get("FREETYPEPY_BUNDLE_FT"):
+ print("# Will build and bundle FreeType.")
diff --git a/p4a/pythonforandroid/recipes/freetype/__init__.py b/p4a/pythonforandroid/recipes/freetype/__init__.py
index 36171ff..0b04c95 100644
--- a/p4a/pythonforandroid/recipes/freetype/__init__.py
+++ b/p4a/pythonforandroid/recipes/freetype/__init__.py
@@ -1,44 +1,132 @@
-from pythonforandroid.toolchain import Recipe
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint, info
from pythonforandroid.util import current_directory
-from pythonforandroid.logger import shprint
-from os.path import exists, join, realpath
+from os.path import join, exists
+from multiprocessing import cpu_count
import sh
class FreetypeRecipe(Recipe):
+ """The freetype library it's special, because has cyclic dependencies with
+ harfbuzz library, so freetype can be build with harfbuzz support, and
+ harfbuzz can be build with freetype support. This complicates the build of
+ both recipes because in order to get the full set we need to compile those
+ recipes several times:
+ - build freetype without harfbuzz
+ - build harfbuzz with freetype
+ - build freetype with harfbuzz support
- version = '2.5.5'
+ .. note::
+ To build freetype with harfbuzz support you must add `harfbuzz` to your
+ requirements, otherwise freetype will be build without harfbuzz
+
+ .. seealso::
+ https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/
+ """
+
+ version = '2.10.1'
url = 'http://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' # noqa
+ built_libraries = {'libfreetype.so': 'objs/.libs'}
- depends = ['harfbuzz']
+ def get_recipe_env(self, arch=None, with_harfbuzz=False):
+ env = super().get_recipe_env(arch)
+ if with_harfbuzz:
+ harfbuzz_build = self.get_recipe(
+ 'harfbuzz', self.ctx
+ ).get_build_dir(arch.arch)
+ freetype_install = join(self.get_build_dir(arch.arch), 'install')
- def should_build(self, arch):
- if exists(join(self.get_build_dir(arch.arch),
- 'objs', '.libs', 'libfreetype.a')):
- return False
- return True
+ env['HARFBUZZ_CFLAGS'] = '-I{harfbuzz} -I{harfbuzz}/src'.format(
+ harfbuzz=harfbuzz_build
+ )
+ env['HARFBUZZ_LIBS'] = (
+ '-L{freetype}/lib -lfreetype '
+ '-L{harfbuzz}/src/.libs -lharfbuzz'.format(
+ freetype=freetype_install, harfbuzz=harfbuzz_build
+ )
+ )
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
+ # android's zlib support
+ zlib_lib_path = arch.ndk_lib_dir_versioned
+ zlib_includes = self.ctx.ndk.sysroot_include_dir
- 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'))])
+ def add_flag_if_not_added(flag, env_key):
+ if flag not in env[env_key]:
+ env[env_key] += flag
+ add_flag_if_not_added(' -I' + zlib_includes, 'CFLAGS')
+ add_flag_if_not_added(' -L' + zlib_lib_path, 'LDFLAGS')
+ add_flag_if_not_added(' -lz', 'LDLIBS')
+
+ return env
+
+ def build_arch(self, arch, with_harfbuzz=False):
+ env = self.get_recipe_env(arch, with_harfbuzz=with_harfbuzz)
+
+ harfbuzz_in_recipes = 'harfbuzz' in self.ctx.recipe_build_order
+ prefix_path = self.get_build_dir(arch.arch)
+ if harfbuzz_in_recipes and not with_harfbuzz:
+ # This is the first time we build freetype and we modify `prefix`,
+ # because we will install the compiled library so later we can
+ # build harfbuzz (with freetype support) using this freetype
+ # installation
+ prefix_path = join(prefix_path, 'install')
+
+ # Configure freetype library
+ config_args = {
+ '--host={}'.format(arch.command_prefix),
+ '--prefix={}'.format(prefix_path),
+ '--without-bzip2',
+ '--with-png=no',
+ }
+ if not harfbuzz_in_recipes:
+ info('Build freetype (without harfbuzz)')
+ config_args = config_args.union(
+ {'--disable-static',
+ '--enable-shared',
+ '--with-harfbuzz=no',
+ '--with-zlib=yes',
+ }
+ )
+ elif not with_harfbuzz:
+ info('Build freetype for First time (without harfbuzz)')
+ # This time we will build our freetype library as static because we
+ # want that the harfbuzz library to have the necessary freetype
+ # symbols/functions, so we avoid to have two freetype shared
+ # libraries which will be confusing and harder to link with them
+ config_args = config_args.union(
+ {'--disable-shared', '--with-harfbuzz=no', '--with-zlib=no'}
+ )
+ else:
+ info('Build freetype for Second time (with harfbuzz)')
+ config_args = config_args.union(
+ {'--disable-static',
+ '--enable-shared',
+ '--with-harfbuzz=yes',
+ '--with-zlib=yes',
+ }
+ )
+ info('Configure args are:\n\t-{}'.format('\n\t-'.join(config_args)))
+
+ # Build freetype library
with current_directory(self.get_build_dir(arch.arch)):
configure = sh.Command('./configure')
- shprint(configure,
- '--host=arm-linux-androideabi',
- '--prefix={}'.format(realpath('.')),
- '--without-zlib',
- '--with-png=no',
- '--disable-shared',
- _env=env)
- shprint(sh.make, '-j5', _env=env)
+ shprint(configure, *config_args, _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
- shprint(sh.cp, 'objs/.libs/libfreetype.a', self.ctx.libs_dir)
+ if not with_harfbuzz and harfbuzz_in_recipes:
+ info('Installing freetype (first time build without harfbuzz)')
+ # First build, install the compiled lib, and clean build env
+ shprint(sh.make, 'install', _env=env)
+ shprint(sh.make, 'distclean', _env=env)
+
+ def install_libraries(self, arch):
+ # This library it's special because the first time we built it may not
+ # generate the expected library, because it can depend on harfbuzz, so
+ # we will make sure to only install it when the library exists
+ if not exists(list(self.get_libraries(arch))[0]):
+ return
+ self.install_libs(arch, *self.get_libraries(arch))
recipe = FreetypeRecipe()
diff --git a/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py b/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py
index 2d1cdb0..901f208 100644
--- a/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py
+++ b/p4a/pythonforandroid/recipes/genericndkbuild/__init__.py
@@ -1,3 +1,5 @@
+from os.path import join
+
from pythonforandroid.recipe import BootstrapNDKRecipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
@@ -7,15 +9,17 @@ class GenericNDKBuildRecipe(BootstrapNDKRecipe):
version = None
url = None
- depends = [('python2', 'python3', 'python3crystax')]
- conflicts = ['sdl2', 'pygame', 'sdl']
+ depends = ['python3']
+ conflicts = ['sdl2']
def should_build(self, arch):
return True
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 = super().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
@@ -23,7 +27,7 @@ class GenericNDKBuildRecipe(BootstrapNDKRecipe):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", _env=env)
+ shprint(sh.Command(join(self.ctx.ndk_dir, "ndk-build")), "V=1", _env=env)
recipe = GenericNDKBuildRecipe()
diff --git a/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py b/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py
deleted file mode 100644
index 598ca13..0000000
--- a/p4a/pythonforandroid/recipes/gevent-websocket/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-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 = ['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 5933fb3..7958a54 100644
--- a/p4a/pythonforandroid/recipes/gevent/__init__.py
+++ b/p4a/pythonforandroid/recipes/gevent/__init__.py
@@ -6,16 +6,17 @@ from pythonforandroid.recipe import CythonRecipe
class GeventRecipe(CythonRecipe):
version = '1.4.0'
url = 'https://pypi.python.org/packages/source/g/gevent/gevent-{version}.tar.gz'
- depends = ['librt', 'greenlet']
+ depends = ['librt', 'setuptools']
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.
+ - Copies all -l from LDLIBS 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)
+ env = super().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()
@@ -24,6 +25,7 @@ class GeventRecipe(CythonRecipe):
# 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['LIBS'] += ' {}'.format(''.join(re.findall(regex, env['LDLIBS'])).strip())
env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS'])
info('Moved "{}" from LDFLAGS to LIBS.'.format(env['LIBS']))
return env
diff --git a/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py b/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py
index 62344f0..873ca61 100644
--- a/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py
+++ b/p4a/pythonforandroid/recipes/groestlcoin_hash/__init__.py
@@ -2,9 +2,9 @@ from pythonforandroid.recipe import CythonRecipe
class GroestlcoinHashRecipe(CythonRecipe):
- version = '1.0.1'
+ version = '1.0.3'
url = 'https://github.com/Groestlcoin/groestlcoin-hash-python/archive/{version}.tar.gz'
- depends = []
+ depends = ['setuptools']
cythonize = False
diff --git a/p4a/pythonforandroid/recipes/harfbuzz/__init__.py b/p4a/pythonforandroid/recipes/harfbuzz/__init__.py
index 32f4e51..fd1dbe9 100644
--- a/p4a/pythonforandroid/recipes/harfbuzz/__init__.py
+++ b/p4a/pythonforandroid/recipes/harfbuzz/__init__.py
@@ -1,39 +1,75 @@
-from pythonforandroid.toolchain import Recipe
+from pythonforandroid.recipe import Recipe
from pythonforandroid.util import current_directory
from pythonforandroid.logger import shprint
-from os.path import exists, join
+from multiprocessing import cpu_count
+from os.path import join
import sh
class HarfbuzzRecipe(Recipe):
- version = '0.9.40'
- url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.bz2' # noqa
+ """The harfbuzz library it's special, because has cyclic dependencies with
+ freetype library, so freetype can be build with harfbuzz support, and
+ harfbuzz can be build with freetype support. This complicates the build of
+ both recipes because in order to get the full set we need to compile those
+ recipes several times:
+ - build freetype without harfbuzz
+ - build harfbuzz with freetype
+ - build freetype with harfbuzz support
- def should_build(self, arch):
- if exists(join(self.get_build_dir(arch.arch),
- 'src', '.libs', 'libharfbuzz.a')):
- return False
- return True
+ .. seealso::
+ https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/
+ """
+
+ version = '2.6.4'
+ url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.xz' # noqa
+ opt_depends = ['freetype']
+ built_libraries = {'libharfbuzz.so': 'src/.libs'}
+
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ if 'freetype' in self.ctx.recipe_build_order:
+ freetype = self.get_recipe('freetype', self.ctx)
+ freetype_install = join(
+ freetype.get_build_dir(arch.arch), 'install'
+ )
+ # Explicitly tell harfbuzz's configure script that we want to
+ # use our freetype library or it won't be correctly detected
+ env['FREETYPE_CFLAGS'] = '-I{}/include/freetype2'.format(
+ freetype_install
+ )
+ env['FREETYPE_LIBS'] = ' '.join(
+ ['-L{}/lib'.format(freetype_install), '-lfreetype']
+ )
+ return env
def build_arch(self, arch):
env = self.get_recipe_env(arch)
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch) +
- '-L{}'.format(self.ctx.libs_dir))
+
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',
- '--disable-shared',
- _env=env)
- shprint(sh.make, '-j5', _env=env)
+ shprint(
+ configure,
+ '--host={}'.format(arch.command_prefix),
+ '--prefix={}'.format(self.get_build_dir(arch.arch)),
+ '--with-freetype={}'.format(
+ 'yes'
+ if 'freetype' in self.ctx.recipe_build_order
+ else 'no'
+ ),
+ '--with-icu=no',
+ '--with-cairo=no',
+ '--with-fontconfig=no',
+ '--with-glib=no',
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
- shprint(sh.cp, '-L', join('src', '.libs', 'libharfbuzz.a'),
- self.ctx.libs_dir)
+ if 'freetype' in self.ctx.recipe_build_order:
+ # Rebuild/install freetype with harfbuzz support
+ freetype = self.get_recipe('freetype', self.ctx)
+ freetype.build_arch(arch, with_harfbuzz=True)
+ freetype.install_libraries(arch)
recipe = HarfbuzzRecipe()
diff --git a/p4a/pythonforandroid/recipes/hostpython2/__init__.py b/p4a/pythonforandroid/recipes/hostpython2/__init__.py
deleted file mode 100644
index 39a75e4..0000000
--- a/p4a/pythonforandroid/recipes/hostpython2/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from pythonforandroid.python import HostPythonRecipe
-
-
-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', 'hostpython3crystax', 'hostpython2legacy']
-
-
-recipe = Hostpython2Recipe()
diff --git a/p4a/pythonforandroid/recipes/hostpython2legacy/Setup b/p4a/pythonforandroid/recipes/hostpython2legacy/Setup
deleted file mode 100644
index d21c893..0000000
--- a/p4a/pythonforandroid/recipes/hostpython2legacy/Setup
+++ /dev/null
@@ -1,495 +0,0 @@
-# -*- makefile -*-
-# The file Setup is used by the makesetup script to construct the files
-# Makefile and config.c, from Makefile.pre and config.c.in,
-# respectively. The file Setup itself is initially copied from
-# Setup.dist; once it exists it will not be overwritten, so you can edit
-# Setup to your heart's content. Note that Makefile.pre is created
-# from Makefile.pre.in by the toplevel configure script.
-
-# (VPATH notes: Setup and Makefile.pre are in the build directory, as
-# are Makefile and config.c; the *.in and *.dist files are in the source
-# directory.)
-
-# Each line in this file describes one or more optional modules.
-# Modules enabled here will not be compiled by the setup.py script,
-# so the file can be used to override setup.py's behavior.
-
-# Lines have the following structure:
-#
-# ... [ ...] [ ...] [ ...]
-#
-# is anything ending in .c (.C, .cc, .c++ are C++ files)
-# is anything starting with -I, -D, -U or -C
-# is anything ending in .a or beginning with -l or -L
-# is anything else but should be a valid Python
-# identifier (letters, digits, underscores, beginning with non-digit)
-#
-# (As the makesetup script changes, it may recognize some other
-# arguments as well, e.g. *.so and *.sl as libraries. See the big
-# case statement in the makesetup script.)
-#
-# Lines can also have the form
-#
-# =
-#
-# which defines a Make variable definition inserted into Makefile.in
-#
-# Finally, if a line contains just the word "*shared*" (without the
-# quotes but with the stars), then the following modules will not be
-# built statically. The build process works like this:
-#
-# 1. Build all modules that are declared as static in Modules/Setup,
-# combine them into libpythonxy.a, combine that into python.
-# 2. Build all modules that are listed as shared in Modules/Setup.
-# 3. Invoke setup.py. That builds all modules that
-# a) are not builtin, and
-# b) are not listed in Modules/Setup, and
-# c) can be build on the target
-#
-# Therefore, modules declared to be shared will not be
-# included in the config.c file, nor in the list of objects to be
-# added to the library archive, and their linker options won't be
-# added to the linker options. Rules to create their .o files and
-# their shared libraries will still be added to the Makefile, and
-# their names will be collected in the Make variable SHAREDMODS. This
-# is used to build modules as shared libraries. (They can be
-# installed using "make sharedinstall", which is implied by the
-# toplevel "make install" target.) (For compatibility,
-# *noconfig* has the same effect as *shared*.)
-#
-# In addition, *static* explicitly declares the following modules to
-# be static. Lines containing "*static*" and "*shared*" may thus
-# alternate throughout this file.
-
-# NOTE: As a standard policy, as many modules as can be supported by a
-# platform should be present. The distribution comes with all modules
-# enabled that are supported by most platforms and don't require you
-# to ftp sources from elsewhere.
-
-
-# Some special rules to define PYTHONPATH.
-# Edit the definitions below to indicate which options you are using.
-# Don't add any whitespace or comments!
-
-# Directories where library files get installed.
-# DESTLIB is for Python modules; MACHDESTLIB for shared libraries.
-DESTLIB=$(LIBDEST)
-MACHDESTLIB=$(BINLIBDEST)
-
-# NOTE: all the paths are now relative to the prefix that is computed
-# at run time!
-
-# Standard path -- don't edit.
-# No leading colon since this is the first entry.
-# Empty since this is now just the runtime prefix.
-DESTPATH=
-
-# Site specific path components -- should begin with : if non-empty
-SITEPATH=
-
-# Standard path components for test modules
-TESTPATH=
-
-# Path components for machine- or system-dependent modules and shared libraries
-MACHDEPPATH=:plat-$(MACHDEP)
-EXTRAMACHDEPPATH=
-
-# Path component for the Tkinter-related modules
-# The TKPATH variable is always enabled, to save you the effort.
-TKPATH=:lib-tk
-
-# Path component for old modules.
-OLDPATH=:lib-old
-
-COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)$(MACHDEPPATH)$(EXTRAMACHDEPPATH)$(TKPATH)$(OLDPATH)
-PYTHONPATH=$(COREPYTHONPATH)
-
-
-# The modules listed here can't be built as shared libraries for
-# 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
-# setup.py script in the root of the Python source tree.
-
-posix posixmodule.c # posix (UNIX) system calls
-errno errnomodule.c # posix (UNIX) errno values
-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
-
-# The zipimport module is always imported at startup. Having it as a
-# builtin module avoids some bootstrapping problems and reduces overhead.
-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
-# appropriate lines below.
-
-# ======================================================================
-
-# The Python symtable module depends on .h files that setup.py doesn't track
-_symtable symtablemodule.c
-
-# The SGI specific GL module:
-
-GLHACK=-Dclear=__GLclear
-#gl glmodule.c cgensupport.c -I$(srcdir) $(GLHACK) -lgl -lX11
-
-# Pure module. Cannot be linked dynamically.
-# -DWITH_QUANTIFY, -DWITH_PURIFY, or -DWITH_ALL_PURE
-#WHICH_PURE_PRODUCTS=-DWITH_ALL_PURE
-#PURE_INCLS=-I/usr/local/include
-#PURE_STUBLIBS=-L/usr/local/lib -lpurify_stubs -lquantify_stubs
-#pure puremodule.c $(WHICH_PURE_PRODUCTS) $(PURE_INCLS) $(PURE_STUBLIBS)
-
-# Uncommenting the following line tells makesetup that all following
-# modules are to be built as shared libraries (see above for more
-# detail; also note that *static* reverses this effect):
-
-#*shared*
-
-# GNU readline. Unlike previous Python incarnations, GNU readline is
-# now incorporated in an optional module, configured in the Setup file
-# instead of by a configure script switch. You may have to insert a
-# -L option pointing to the directory where libreadline.* lives,
-# and you may have to change -ltermcap to -ltermlib or perhaps remove
-# it, depending on your system -- see the GNU readline instructions.
-# It's okay for this to be a shared library, too.
-
-#readline readline.c -lreadline -ltermcap
-
-
-# Modules that should always be present (non UNIX dependent):
-
-array arraymodule.c # array objects
-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
-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
-#_pickle _pickle.c # pickle accelerator
-datetime datetimemodule.c # date/time type
-_bisect _bisectmodule.c # Bisection algorithms
-
-#unicodedata unicodedata.c # static Unicode character database
-
-# access to ISO C locale support
-#_locale _localemodule.c # -lintl
-
-
-# Modules with some UNIX dependencies -- on by default:
-# (If you have a really backward UNIX, select and socket may not be
-# supported...)
-
-fcntl fcntlmodule.c # fcntl(2) and ioctl(2)
-#spwd spwdmodule.c # spwd(3)
-#grp grpmodule.c # grp(3)
-select selectmodule.c # select(2); not on ancient System V
-
-# Memory-mapped files (also works on Win32).
-#mmap mmapmodule.c
-
-# CSV file helper
-#_csv _csv.c
-
-# Socket module helper for socket(2)
-_socket socketmodule.c
-
-# Socket module helper for SSL support; you must comment out the other
-# socket line above, and possibly edit the SSL variable:
-#SSL=/usr/local/ssl
-#_ssl _ssl.c \
-# -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-# -L$(SSL)/lib -lssl -lcrypto
-
-# The crypt module is now disabled by default because it breaks builds
-# on many systems (where -lcrypt is needed), e.g. Linux (I believe).
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
-
-
-# Some more UNIX dependent modules -- off by default, since these
-# are not supported by all UNIX systems:
-
-#nis nismodule.c -lnsl # Sun yellow pages -- not everywhere
-#termios termios.c # Steen Lumholt's termios module
-#resource resource.c # Jeremy Hylton's rlimit interface
-
-
-# Multimedia modules -- off by default.
-# These don't work for 64-bit platforms!!!
-# #993173 says audioop works on 64-bit platforms, though.
-# These represent audio samples or images as strings:
-
-#audioop audioop.c # Operations on audio samples
-#imageop imageop.c # Operations on images
-
-
-# Note that the _md5 and _sha modules are normally only built if the
-# system does not have the OpenSSL libs containing an optimized version.
-
-# The _md5 module implements the RSA Data Security, Inc. MD5
-# Message-Digest Algorithm, described in RFC 1321. The necessary files
-# md5.c and md5.h are included here.
-
-_md5 md5module.c md5.c
-
-
-# The _sha module implements the SHA checksum algorithms.
-# (NIST's Secure Hash Algorithms.)
-_sha shamodule.c
-_sha256 sha256module.c
-_sha512 sha512module.c
-
-
-# SGI IRIX specific modules -- off by default.
-
-# These module work on any SGI machine:
-
-# *** gl must be enabled higher up in this file ***
-#fm fmmodule.c $(GLHACK) -lfm -lgl # Font Manager
-#sgi sgimodule.c # sgi.nap() and a few more
-
-# This module requires the header file
-# /usr/people/4Dgifts/iristools/include/izoom.h:
-#imgfile imgfile.c -limage -lgutil -lgl -lm # Image Processing Utilities
-
-
-# These modules require the Multimedia Development Option (I think):
-
-#al almodule.c -laudio # Audio Library
-#cd cdmodule.c -lcdaudio -lds -lmediad # CD Audio Library
-#cl clmodule.c -lcl -lawareaudio # Compression Library
-#sv svmodule.c yuvconvert.c -lsvideo -lXext -lX11 # Starter Video
-
-
-# The FORMS library, by Mark Overmars, implements user interface
-# components such as dialogs and buttons using SGI's GL and FM
-# libraries. You must ftp the FORMS library separately from
-# ftp://ftp.cs.ruu.nl/pub/SGI/FORMS. It was tested with FORMS 2.2a.
-# NOTE: if you want to be able to use FORMS and curses simultaneously
-# (or both link them statically into the same binary), you must
-# compile all of FORMS with the cc option "-Dclear=__GLclear".
-
-# The FORMS variable must point to the FORMS subdirectory of the forms
-# toplevel directory:
-
-#FORMS=/ufs/guido/src/forms/FORMS
-#fl flmodule.c -I$(FORMS) $(GLHACK) $(FORMS)/libforms.a -lfm -lgl
-
-
-# SunOS specific modules -- off by default:
-
-#sunaudiodev sunaudiodev.c
-
-
-# A Linux specific module -- off by default; this may also work on
-# some *BSDs.
-
-#linuxaudiodev linuxaudiodev.c
-
-
-# George Neville-Neil's timing module:
-
-#timing timingmodule.c
-
-
-# The _tkinter module.
-#
-# The command for _tkinter is long and site specific. Please
-# uncomment and/or edit those parts as indicated. If you don't have a
-# specific extension (e.g. Tix or BLT), leave the corresponding line
-# commented out. (Leave the trailing backslashes in! If you
-# experience strange errors, you may want to join all uncommented
-# lines and remove the backslashes -- the backslash interpretation is
-# done by the shell's "read" command and it may not be implemented on
-# every system.
-
-# *** Always uncomment this (leave the leading underscore in!):
-# _tkinter _tkinter.c tkappinit.c -DWITH_APPINIT \
-# *** Uncomment and edit to reflect where your Tcl/Tk libraries are:
-# -L/usr/local/lib \
-# *** Uncomment and edit to reflect where your Tcl/Tk headers are:
-# -I/usr/local/include \
-# *** Uncomment and edit to reflect where your X11 header files are:
-# -I/usr/X11R6/include \
-# *** Or uncomment this for Solaris:
-# -I/usr/openwin/include \
-# *** Uncomment and edit for Tix extension only:
-# -DWITH_TIX -ltix8.1.8.2 \
-# *** Uncomment and edit for BLT extension only:
-# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \
-# *** Uncomment and edit for PIL (TkImaging) extension only:
-# (See http://www.pythonware.com/products/pil/ for more info)
-# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \
-# *** Uncomment and edit for TOGL extension only:
-# -DWITH_TOGL togl.c \
-# *** Uncomment and edit to reflect your Tcl/Tk versions:
-# -ltk8.2 -ltcl8.2 \
-# *** Uncomment and edit to reflect where your X11 libraries are:
-# -L/usr/X11R6/lib \
-# *** Or uncomment this for Solaris:
-# -L/usr/openwin/lib \
-# *** Uncomment these for TOGL extension only:
-# -lGL -lGLU -lXext -lXmu \
-# *** Uncomment for AIX:
-# -lld \
-# *** Always uncomment this; X11 libraries to link with:
-# -lX11
-
-# Lance Ellinghaus's syslog module
-#syslog syslogmodule.c # syslog daemon interface
-
-
-# Curses support, requring the System V version of curses, often
-# provided by the ncurses library. e.g. on Linux, link with -lncurses
-# instead of -lcurses).
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#_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
-
-
-# Generic (SunOS / SVR4) dynamic loading module.
-# This is not needed for dynamic loading of Python modules --
-# it is a highly experimental and dangerous device for calling
-# *arbitrary* C functions in *arbitrary* shared libraries:
-
-#dl dlmodule.c
-
-
-# Modules that provide persistent dictionary-like semantics. You will
-# probably want to arrange for at least one of them to be available on
-# your machine, though none are defined by default because of library
-# dependencies. The Python module anydbm.py provides an
-# implementation independent wrapper for these; dumbdbm.py provides
-# similar functionality (but slower of course) implemented in Python.
-
-# The standard Unix dbm module has been moved to Setup.config so that
-# it will be compiled as a shared library by default. Compiling it as
-# a built-in module causes conflicts with the pybsddb3 module since it
-# creates a static dependency on an out-of-date version of db.so.
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#dbm dbmmodule.c # dbm(3) may require -lndbm or similar
-
-# Anthony Baxter's gdbm module. GNU dbm(3) will require -lgdbm:
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#gdbm gdbmmodule.c -I/usr/local/include -L/usr/local/lib -lgdbm
-
-
-# Sleepycat Berkeley DB interface.
-#
-# This requires the Sleepycat DB code, see http://www.sleepycat.com/
-# The earliest supported version of that library is 3.0, the latest
-# supported version is 4.0 (4.1 is specifically not supported, as that
-# changes the semantics of transactional databases). A list of available
-# releases can be found at
-#
-# http://www.sleepycat.com/update/index.html
-#
-# Edit the variables DB and DBLIBVERto point to the db top directory
-# and the subdirectory of PORT where you built it.
-#DB=/usr/local/BerkeleyDB.4.0
-#DBLIBVER=4.0
-#DBINC=$(DB)/include
-#DBLIB=$(DB)/lib
-#_bsddb _bsddb.c -I$(DBINC) -L$(DBLIB) -ldb-$(DBLIBVER)
-
-# Historical Berkeley DB 1.85
-#
-# This module is deprecated; the 1.85 version of the Berkeley DB library has
-# bugs that can cause data corruption. If you can, use later versions of the
-# library instead, available from .
-
-#DB=/depot/sundry/src/berkeley-db/db.1.85
-#DBPORT=$(DB)/PORT/irix.5.3
-#bsddb185 bsddbmodule.c -I$(DBPORT)/include -I$(DBPORT) $(DBPORT)/libdb.a
-
-
-
-# Helper module for various ascii-encoders
-binascii binascii.c
-
-# Fred Drake's interface to the Python parser
-parser parsermodule.c
-
-# cStringIO and cPickle
-cStringIO cStringIO.c
-cPickle cPickle.c
-
-
-# Lee Busby's SIGFPE modules.
-# The library to link fpectl with is platform specific.
-# Choose *one* of the options below for fpectl:
-
-# For SGI IRIX (tested on 5.3):
-#fpectl fpectlmodule.c -lfpe
-
-# For Solaris with SunPro compiler (tested on Solaris 2.5 with SunPro C 4.2):
-# (Without the compiler you don't have -lsunmath.)
-#fpectl fpectlmodule.c -R/opt/SUNWspro/lib -lsunmath -lm
-
-# For other systems: see instructions in fpectlmodule.c.
-#fpectl fpectlmodule.c ...
-
-# Test module for fpectl. No extra libraries needed.
-#fpetest fpetestmodule.c
-
-# 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
-
-# Interface to the Expat XML parser
-#
-# Expat was written by James Clark and is now maintained by a group of
-# developers on SourceForge; see www.libexpat.org for more
-# information. The pyexpat module was written by Paul Prescod after a
-# prototype by Jack Jansen. Source of Expat 1.95.2 is included in
-# Modules/expat/. Usage of a system shared libexpat.so/expat.dll is
-# not advised.
-#
-# More information on Expat can be found at www.libexpat.org.
-#
-pyexpat expat/xmlparse.c expat/xmlrole.c expat/xmltok.c pyexpat.c -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI
-
-
-# Hye-Shik Chang's CJKCodecs
-
-# multibytecodec is required for all the other CJK codec modules
-#_multibytecodec cjkcodecs/multibytecodec.c
-
-#_codecs_cn cjkcodecs/_codecs_cn.c
-#_codecs_hk cjkcodecs/_codecs_hk.c
-#_codecs_iso2022 cjkcodecs/_codecs_iso2022.c
-#_codecs_jp cjkcodecs/_codecs_jp.c
-#_codecs_kr cjkcodecs/_codecs_kr.c
-#_codecs_tw cjkcodecs/_codecs_tw.c
-
-# Example -- included for reference only:
-# xx xxmodule.c
-
-# Another example -- the 'xxsubtype' module shows C-level subtyping in action
-xxsubtype xxsubtype.c
diff --git a/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py b/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py
deleted file mode 100644
index 0a02573..0000000
--- a/p4a/pythonforandroid/recipes/hostpython2legacy/__init__.py
+++ /dev/null
@@ -1,67 +0,0 @@
-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
deleted file mode 100644
index 25d4599..0000000
--- a/p4a/pythonforandroid/recipes/hostpython2legacy/fix-segfault-pygchead.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-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 8b268bd..ef2324a 100644
--- a/p4a/pythonforandroid/recipes/hostpython3/__init__.py
+++ b/p4a/pythonforandroid/recipes/hostpython3/__init__.py
@@ -1,17 +1,144 @@
-from pythonforandroid.python import HostPythonRecipe
+import sh
+import os
+
+from multiprocessing import cpu_count
+from pathlib import Path
+from os.path import join
+
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import (
+ BuildInterruptingException,
+ current_directory,
+ ensure_dir,
+)
+from pythonforandroid.prerequisites import OpenSSLPrerequisite
+
+HOSTPYTHON_VERSION_UNSET_MESSAGE = (
+ 'The hostpython recipe must have set version'
+)
+
+SETUP_DIST_NOT_FIND_MESSAGE = (
+ 'Could not find Setup.dist or Setup in Python build'
+)
-class Hostpython3Recipe(HostPythonRecipe):
+class HostPython3Recipe(Recipe):
'''
The hostpython3's recipe.
+ .. versionchanged:: 2019.10.06.post0
+ Refactored from deleted class ``python.HostPythonRecipe`` into here.
+
.. versionchanged:: 0.6.0
Refactored into the new class
:class:`~pythonforandroid.python.HostPythonRecipe`
'''
- version = '3.7.1'
+
+ version = '3.9.9'
name = 'hostpython3'
- conflicts = ['hostpython2', 'hostpython3crystax']
+
+ build_subdir = 'native-build'
+ '''Specify the sub build directory for the hostpython3 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`.'''
+
+ patches = ['patches/pyconfig_detection.patch']
+
+ @property
+ def _exe_name(self):
+ '''
+ Returns the name of the python executable depending on the version.
+ '''
+ if not self.version:
+ raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE)
+ return f'python{self.version.split(".")[0]}'
+
+ @property
+ def python_exe(self):
+ '''Returns the full path of the hostpython executable.'''
+ return join(self.get_path_to_python(), self._exe_name)
+
+ def get_recipe_env(self, arch=None):
+ env = os.environ.copy()
+ openssl_prereq = OpenSSLPrerequisite()
+ if env.get("PKG_CONFIG_PATH", ""):
+ env["PKG_CONFIG_PATH"] = os.pathsep.join(
+ [openssl_prereq.pkg_config_location, env["PKG_CONFIG_PATH"]]
+ )
+ else:
+ env["PKG_CONFIG_PATH"] = openssl_prereq.pkg_config_location
+ return env
+
+ def should_build(self, arch):
+ if Path(self.python_exe).exists():
+ # no need to build, but we must set hostpython for our Context
+ self.ctx.hostpython = self.python_exe
+ return False
+ return True
+
+ 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):
+ env = self.get_recipe_env(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)
+
+ # Configure the build
+ with current_directory(build_dir):
+ if not Path('config.status').exists():
+ shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env)
+
+ with current_directory(recipe_build_dir):
+ # Create the Setup file. This copying from Setup.dist is
+ # the normal and expected procedure before Python 3.8, but
+ # after this the file with default options is already named "Setup"
+ setup_dist_location = join('Modules', 'Setup.dist')
+ if Path(setup_dist_location).exists():
+ shprint(sh.cp, setup_dist_location,
+ join(build_dir, 'Modules', 'Setup'))
+ else:
+ # Check the expected file does exist
+ setup_location = join('Modules', 'Setup')
+ if not Path(setup_location).exists():
+ raise BuildInterruptingException(
+ SETUP_DIST_NOT_FIND_MESSAGE
+ )
+
+ shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env)
+
+ # make a copy of the python executable giving it the name we want,
+ # because we got different python's executable names depending on
+ # the fs being case-insensitive (Mac OS X, Cygwin...) or
+ # case-sensitive (linux)...so this way we will have an unique name
+ # for our hostpython, regarding the used fs
+ for exe_name in ['python.exe', 'python']:
+ exe = join(self.get_path_to_python(), exe_name)
+ if Path(exe).is_file():
+ shprint(sh.cp, exe, self.python_exe)
+ break
+
+ self.ctx.hostpython = self.python_exe
-recipe = Hostpython3Recipe()
+recipe = HostPython3Recipe()
diff --git a/p4a/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch b/p4a/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch
new file mode 100644
index 0000000..7f78b66
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch
@@ -0,0 +1,13 @@
+diff -Nru Python-3.8.2/Lib/site.py Python-3.8.2-new/Lib/site.py
+--- Python-3.8.2/Lib/site.py 2020-04-28 12:48:38.000000000 -0700
++++ Python-3.8.2-new/Lib/site.py 2020-04-28 12:52:46.000000000 -0700
+@@ -487,7 +487,8 @@
+ if key == 'include-system-site-packages':
+ system_site = value.lower()
+ elif key == 'home':
+- sys._home = value
++ # this is breaking pyconfig.h path detection with venv
++ print('Ignoring "sys._home = value" override', file=sys.stderr)
+
+ sys.prefix = sys.exec_prefix = site_prefix
+
diff --git a/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py b/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py
deleted file mode 100644
index 88cee35..0000000
--- a/p4a/pythonforandroid/recipes/hostpython3crystax/__init__.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from pythonforandroid.toolchain import Recipe, shprint
-from os.path import join
-import sh
-
-
-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'
- # 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 = Hostpython3CrystaXRecipe()
diff --git a/p4a/pythonforandroid/recipes/icu/__init__.py b/p4a/pythonforandroid/recipes/icu/__init__.py
index 4bb2de0..232939b 100644
--- a/p4a/pythonforandroid/recipes/icu/__init__.py
+++ b/p4a/pythonforandroid/recipes/icu/__init__.py
@@ -1,33 +1,57 @@
import sh
import os
-from os.path import join, isdir
-from pythonforandroid.recipe import NDKRecipe
+import platform
+from os.path import join, isdir, exists
+from multiprocessing import cpu_count
+from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import shprint
from pythonforandroid.util import current_directory, ensure_dir
-class ICURecipe(NDKRecipe):
+class ICURecipe(Recipe):
name = 'icu4c'
version = '57.1'
- url = 'http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-src.tgz'
+ major_version = version.split('.')[0]
+ url = (
+ "https://github.com/unicode-org/icu/releases/download/"
+ "release-{version_hyphen}/icu4c-{version_underscore}-src.tgz"
+ )
- depends = [('hostpython2', 'hostpython3')] # installs in python
- generated_libraries = [
- 'libicui18n.so', 'libicuuc.so', 'libicudata.so', 'libicule.so']
+ depends = ['hostpython3'] # installs in python
+ patches = ['disable-libs-version.patch']
- def get_lib_dir(self, arch):
- lib_dir = join(self.ctx.get_python_install_dir(), "lib")
- ensure_dir(lib_dir)
- return lib_dir
+ built_libraries = {
+ 'libicui18n{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicuuc{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicudata{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicule{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicuio{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicutu{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libiculx{}.so'.format(major_version): 'build_icu_android/lib',
+ }
- def prepare_build_dir(self, arch):
- if self.ctx.android_api > 19:
- # greater versions do not have /usr/include/sys/exec_elf.h
- raise RuntimeError("icu needs an android api <= 19")
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(
+ version=self.version,
+ version_underscore=self.version.replace('.', '_'),
+ version_hyphen=self.version.replace('.', '-'))
- super(ICURecipe, self).prepare_build_dir(arch)
+ def get_recipe_dir(self):
+ """
+ .. note:: We need to overwrite `Recipe.get_recipe_dir` due to the
+ mismatch name between the recipe's folder (icu) and the value
+ of `ICURecipe.name` (icu4c).
+ """
+ if self.ctx.local_recipes is not None:
+ local_recipe_dir = join(self.ctx.local_recipes, 'icu')
+ if exists(local_recipe_dir):
+ return local_recipe_dir
+ return join(self.ctx.root_dir, 'recipes', 'icu')
- def build_arch(self, arch, *extra_args):
+ def build_arch(self, arch):
env = self.get_recipe_env(arch).copy()
build_root = self.get_build_dir(arch.arch)
@@ -40,7 +64,7 @@ class ICURecipe(NDKRecipe):
return build_dest, True
icu_build = join(build_root, "icu_build")
- build_linux, exists = make_build_dest("build_icu_linux")
+ build_host, exists = make_build_dest("build_icu_host")
host_env = os.environ.copy()
# reduce the function set
@@ -51,102 +75,53 @@ class ICURecipe(NDKRecipe):
"-DUCONFIG_NO_TRANSLITERATION=0 ")
if not exists:
+ icu4c_host_platform = platform.system()
+ if icu4c_host_platform == "Darwin":
+ icu4c_host_platform = "MacOSX"
configure = sh.Command(
join(build_root, "source", "runConfigureICU"))
- with current_directory(build_linux):
+ with current_directory(build_host):
shprint(
configure,
- "Linux",
+ icu4c_host_platform,
"--prefix="+icu_build,
"--enable-extras=no",
"--enable-strict=no",
- "--enable-static",
+ "--enable-static=no",
"--enable-tests=no",
"--enable-samples=no",
_env=host_env)
- shprint(sh.make, "-j5", _env=host_env)
+ shprint(sh.make, "-j", str(cpu_count()), _env=host_env)
shprint(sh.make, "install", _env=host_env)
-
build_android, exists = make_build_dest("build_icu_android")
if not exists:
-
configure = sh.Command(join(build_root, "source", "configure"))
- include = (
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/include/"
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/"
- "{arch}/include")
- include = include.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["CPPFLAGS"] = env["CXXFLAGS"] + " "
- env["CPPFLAGS"] += host_env["CPPFLAGS"]
- env["CPPFLAGS"] += include
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["LDFLAGS"] += " -lgnustl_shared -L"+lib
-
- env.pop("CFLAGS", None)
- env.pop("CXXFLAGS", None)
-
with current_directory(build_android):
shprint(
configure,
- "--with-cross-build="+build_linux,
+ "--with-cross-build="+build_host,
"--enable-extras=no",
"--enable-strict=no",
- "--enable-static",
+ "--enable-static=no",
"--enable-tests=no",
"--enable-samples=no",
- "--host="+env["TOOLCHAIN_PREFIX"],
+ "--host="+arch.command_prefix,
"--prefix="+icu_build,
_env=env)
- shprint(sh.make, "-j5", _env=env)
+ shprint(sh.make, "-j", str(cpu_count()), _env=env)
shprint(sh.make, "install", _env=env)
- self.copy_files(arch)
-
- def copy_files(self, arch):
- env = self.get_recipe_env(arch)
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- stl_lib = join(lib, "libgnustl_shared.so")
- dst_dir = join(self.ctx.get_site_packages_dir(), "..", "lib-dynload")
- shprint(sh.cp, stl_lib, dst_dir)
-
- src_lib = join(self.get_build_dir(arch.arch), "icu_build", "lib")
- dst_lib = self.get_lib_dir(arch)
-
- src_suffix = "." + self.version
- dst_suffix = "." + self.version.split(".")[0] # main version
- for lib in self.generated_libraries:
- shprint(sh.cp, join(src_lib, lib+src_suffix),
- join(dst_lib, lib+dst_suffix))
+ def install_libraries(self, arch):
+ super().install_libraries(arch)
src_include = join(
self.get_build_dir(arch.arch), "icu_build", "include")
dst_include = join(
- self.ctx.get_python_install_dir(), "include", "icu")
+ self.ctx.get_python_install_dir(arch.arch), "include", "icu")
ensure_dir(dst_include)
shprint(sh.cp, "-r", join(src_include, "layout"), dst_include)
shprint(sh.cp, "-r", join(src_include, "unicode"), dst_include)
- # copy stl library
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- stl_lib = join(lib, "libgnustl_shared.so")
-
- dst_dir = join(self.ctx.get_python_install_dir(), "lib")
- ensure_dir(dst_dir)
- shprint(sh.cp, stl_lib, dst_dir)
-
recipe = ICURecipe()
diff --git a/p4a/pythonforandroid/recipes/icu/disable-libs-version.patch b/p4a/pythonforandroid/recipes/icu/disable-libs-version.patch
new file mode 100644
index 0000000..872abe0
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/icu/disable-libs-version.patch
@@ -0,0 +1,66 @@
+diff -aur icu4c-org/source/config/Makefile.inc.in icu4c/source/config/Makefile.inc.in
+--- icu/source/config/Makefile.inc.in.orig 2016-03-23 21:50:50.000000000 +0100
++++ icu/source/config/Makefile.inc.in 2019-02-15 17:59:28.331749766 +0100
+@@ -142,8 +142,8 @@
+ LDLIBRARYPATH_ENVVAR = LD_LIBRARY_PATH
+
+ # Versioned target for a shared library
+-FINAL_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION)
+-MIDDLE_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION_MAJOR)
++FINAL_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION)
++MIDDLE_SO_TARGET = $(SO_TARGET)
+
+ # Access to important ICU tools.
+ # Use as follows: $(INVOKE) $(GENRB) arguments ..
+diff -aur icu4c-org/source/config/mh-linux icu4c/source/config/mh-linux
+--- icu4c-org/source/config/mh-linux 2017-07-05 13:23:06.000000000 +0200
++++ icu4c/source/config/mh-linux 2017-07-06 14:02:52.275016858 +0200
+@@ -24,9 +24,17 @@
+
+ ## Compiler switch to embed a library name
+ # The initial tab in the next line is to prevent icu-config from reading it.
+- LD_SONAME = -Wl,-soname -Wl,$(notdir $(MIDDLE_SO_TARGET))
++ LD_SONAME = -Wl,-soname -Wl,$(notdir $(SO_TARGET))
++ DATA_STUBNAME = data$(SO_TARGET_VERSION_MAJOR)
++ COMMON_STUBNAME = uc$(SO_TARGET_VERSION_MAJOR)
++ I18N_STUBNAME = i18n$(SO_TARGET_VERSION_MAJOR)
++ LAYOUT_STUBNAME = le$(SO_TARGET_VERSION_MAJOR)
++ LAYOUTEX_STUBNAME = lx$(SO_TARGET_VERSION_MAJOR)
++ IO_STUBNAME = io$(SO_TARGET_VERSION_MAJOR)
++ TOOLUTIL_STUBNAME = tu$(SO_TARGET_VERSION_MAJOR)
++ CTESTFW_STUBNAME = test$(SO_TARGET_VERSION_MAJOR)
+ #SH# # We can't depend on MIDDLE_SO_TARGET being set.
+-#SH# LD_SONAME=
++#SH# LD_SONAME=$(SO_TARGET)
+
+ ## Shared library options
+ LD_SOOPTIONS= -Wl,-Bsymbolic
+@@ -64,10 +64,10 @@
+
+ ## Versioned libraries rules
+
+-%.$(SO).$(SO_TARGET_VERSION_MAJOR): %.$(SO).$(SO_TARGET_VERSION)
+- $(RM) $@ && ln -s ${ use libifaddrs instead
++if not hasattr(libc, 'getifaddrs'):
++ libc = ctypes.CDLL(ctypes.util.find_library('ifaddrs'), use_errno=True)
++
+ def get_adapters():
+
+ addr0 = addr = ctypes.POINTER(ifaddrs)()
diff --git a/p4a/pythonforandroid/recipes/ifaddrs/__init__.py b/p4a/pythonforandroid/recipes/ifaddrs/__init__.py
index 47c0008..7d44f9c 100644
--- a/p4a/pythonforandroid/recipes/ifaddrs/__init__.py
+++ b/p4a/pythonforandroid/recipes/ifaddrs/__init__.py
@@ -10,7 +10,7 @@ from pythonforandroid.toolchain import current_directory
class IFAddrRecipe(CompiledComponentsPythonRecipe):
version = '8f9a87c'
url = 'https://github.com/morristech/android-ifaddrs/archive/{version}.zip'
- depends = [('hostpython2', 'hostpython3')]
+ depends = ['hostpython3']
call_hostpython_via_targetpython = False
site_packages_name = 'ifaddrs'
diff --git a/p4a/pythonforandroid/recipes/ipaddress/__init__.py b/p4a/pythonforandroid/recipes/ipaddress/__init__.py
deleted file mode 100644
index edc9f42..0000000
--- a/p4a/pythonforandroid/recipes/ipaddress/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class IpaddressRecipe(PythonRecipe):
- 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 6338a52..17168e8 100644
--- a/p4a/pythonforandroid/recipes/jedi/__init__.py
+++ b/p4a/pythonforandroid/recipes/jedi/__init__.py
@@ -5,8 +5,6 @@ class JediRecipe(PythonRecipe):
version = 'v0.9.0'
url = 'https://github.com/davidhalter/jedi/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax', 'python3')]
-
patches = ['fix_MergedNamesDict_get.patch']
# This apparently should be fixed in jedi 0.10 (not released to
# pypi yet), but it still occurs on Android, I could not reproduce
diff --git a/p4a/pythonforandroid/recipes/jpeg/__init__.py b/p4a/pythonforandroid/recipes/jpeg/__init__.py
index 1969d2c..a81b825 100644
--- a/p4a/pythonforandroid/recipes/jpeg/__init__.py
+++ b/p4a/pythonforandroid/recipes/jpeg/__init__.py
@@ -1,9 +1,7 @@
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
+from os.path import join
import sh
@@ -16,15 +14,11 @@ class JpegRecipe(Recipe):
name = 'jpeg'
version = '2.0.1'
url = 'https://github.com/libjpeg-turbo/libjpeg-turbo/archive/{version}.tar.gz' # noqa
+ built_libraries = {'libjpeg.a': '.', 'libturbojpeg.a': '.'}
# we will require this below patch to build the shared library
# patches = ['remove-version.patch']
- def should_build(self, arch):
- return not exists(join(self.get_build_dir(arch.arch),
- 'libturbojpeg.a'))
-
def build_arch(self, arch):
- super(JpegRecipe, self).build_arch(arch)
build_dir = self.get_build_dir(arch.arch)
# TODO: Fix simd/neon
@@ -36,14 +30,12 @@ class JpegRecipe(Recipe):
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_C_COMPILER={cc}'.format(cc=arch.get_clang_exe()),
+ '-DCMAKE_CXX_COMPILER={cc_plus}'.format(
+ cc_plus=arch.get_clang_exe(plus_plus=True)),
'-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_INSTALL_PREFIX=./install',
'-DCMAKE_TOOLCHAIN_FILE=' + toolchain_file,
@@ -59,20 +51,5 @@ class JpegRecipe(Recipe):
_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/kivy/__init__.py b/p4a/pythonforandroid/recipes/kivy/__init__.py
index d21107f..bc9041a 100644
--- a/p4a/pythonforandroid/recipes/kivy/__init__.py
+++ b/p4a/pythonforandroid/recipes/kivy/__init__.py
@@ -1,20 +1,41 @@
+import glob
+from os.path import basename, exists, join
+import sys
+import packaging.version
+
+import sh
from pythonforandroid.recipe import CythonRecipe
from pythonforandroid.toolchain import current_directory, shprint
-from os.path import exists, join, basename
-import sh
-import glob
+
+
+def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None):
+ with current_directory(join(recipe.get_build_dir(arch.arch), "kivy")):
+ kivy_version = shprint(
+ sh.Command(sys.executable),
+ "-c",
+ "import _version; print(_version.__version__)",
+ )
+
+ return packaging.version.parse(
+ str(kivy_version)
+ ) < packaging.version.Version("2.2.0.dev0")
class KivyRecipe(CythonRecipe):
- # post kivy==1.10.1, `fixes SDL2 image loading (jpg)`
- version = 'c4d6894'
+ version = '2.1.0'
url = 'https://github.com/kivy/kivy/archive/{version}.zip'
name = 'kivy'
- depends = [('sdl2', 'pygame'), 'pyjnius']
+ depends = ['sdl2', 'pyjnius', 'setuptools']
+ python_depends = ['certifi']
+
+ # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock.
+ # See: https://github.com/kivy/kivy/pull/8025
+ # WARNING: Remove this patch when a new Kivy version is released.
+ patches = [("sdl-gl-swapwindow-nogil.patch", is_kivy_affected_by_deadlock_issue)]
def cythonize_build(self, env, build_dir='.'):
- super(KivyRecipe, self).cythonize_build(env, build_dir=build_dir)
+ super().cythonize_build(env, build_dir=build_dir)
if not exists(join(build_dir, 'kivy', 'include')):
return
@@ -35,19 +56,22 @@ class KivyRecipe(CythonRecipe):
do_not_cythonize = ['window_x11.pyx', ]
if basename(filename) in do_not_cythonize:
return
- super(KivyRecipe, self).cythonize_file(env, build_dir, filename)
+ super().cythonize_file(env, build_dir, filename)
def get_recipe_env(self, arch):
- env = super(KivyRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
if 'sdl2' in self.ctx.recipe_build_order:
env['USE_SDL2'] = '1'
env['KIVY_SPLIT_EXAMPLES'] = '1'
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
env['KIVY_SDL2_PATH'] = ':'.join([
join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include'),
join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'),
- join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer'),
+ *sdl2_mixer_recipe.get_include_dirs(arch),
join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),
- ])
+ ])
return env
diff --git a/p4a/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch b/p4a/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch
new file mode 100644
index 0000000..8a7c33a
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch
@@ -0,0 +1,32 @@
+diff --git a/kivy/core/window/_window_sdl2.pyx b/kivy/core/window/_window_sdl2.pyx
+index 46e15ec63..5002cd0f9 100644
+--- a/kivy/core/window/_window_sdl2.pyx
++++ b/kivy/core/window/_window_sdl2.pyx
+@@ -746,7 +746,13 @@ cdef class _WindowSDL2Storage:
+ pass
+
+ def flip(self):
+- SDL_GL_SwapWindow(self.win)
++ # On Android (and potentially other platforms), SDL_GL_SwapWindow may
++ # lock the thread waiting for a mutex from another thread to be
++ # released. Calling SDL_GL_SwapWindow with the GIL released allow the
++ # other thread to run (e.g. to process the event filter callback) and
++ # release the mutex SDL_GL_SwapWindow is waiting for.
++ with nogil:
++ SDL_GL_SwapWindow(self.win)
+
+ def save_bytes_in_png(self, filename, data, int width, int height):
+ cdef SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(
+diff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi
+index 6a539de6d..3a5a69d23 100644
+--- a/kivy/lib/sdl2.pxi
++++ b/kivy/lib/sdl2.pxi
+@@ -627,7 +627,7 @@ cdef extern from "SDL.h":
+ cdef SDL_GLContext SDL_GL_GetCurrentContext()
+ cdef int SDL_GL_SetSwapInterval(int interval)
+ cdef int SDL_GL_GetSwapInterval()
+- cdef void SDL_GL_SwapWindow(SDL_Window * window)
++ cdef void SDL_GL_SwapWindow(SDL_Window * window) nogil
+ cdef void SDL_GL_DeleteContext(SDL_GLContext context)
+
+ cdef int SDL_NumJoysticks()
diff --git a/p4a/pythonforandroid/recipes/kivy3/__init__.py b/p4a/pythonforandroid/recipes/kivy3/__init__.py
new file mode 100644
index 0000000..6f27f62
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/kivy3/__init__.py
@@ -0,0 +1,21 @@
+from pythonforandroid.recipe import PythonRecipe
+import shutil
+
+
+class Kivy3Recipe(PythonRecipe):
+ version = 'master'
+ url = 'https://github.com/kivy/kivy3/archive/{version}.zip'
+
+ depends = ['kivy']
+ site_packages_name = 'kivy3'
+
+ '''Due to setuptools.'''
+ call_hostpython_via_targetpython = False
+
+ def build_arch(self, arch):
+ super().build_arch(arch)
+ suffix = '/kivy3/default.glsl'
+ shutil.copyfile(self.get_build_dir(arch.arch) + suffix, self.ctx.get_python_install_dir(arch.arch) + suffix)
+
+
+recipe = Kivy3Recipe()
diff --git a/p4a/pythonforandroid/recipes/kiwisolver/__init__.py b/p4a/pythonforandroid/recipes/kiwisolver/__init__.py
index ae6fa17..587c2b9 100644
--- a/p4a/pythonforandroid/recipes/kiwisolver/__init__.py
+++ b/p4a/pythonforandroid/recipes/kiwisolver/__init__.py
@@ -3,9 +3,9 @@ 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 = ['setuptools']
+ version = '1.3.2'
+ url = 'https://github.com/nucleic/kiwi/archive/{version}.zip'
+ depends = ['cppy']
recipe = KiwiSolverRecipe()
diff --git a/p4a/pythonforandroid/recipes/lapack/__init__.py b/p4a/pythonforandroid/recipes/lapack/__init__.py
new file mode 100644
index 0000000..ae20e69
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/lapack/__init__.py
@@ -0,0 +1,79 @@
+'''
+known to build with cmake version 3.23.2 and NDK r21e.
+See https://gitlab.kitware.com/cmake/cmake/-/issues/18739
+'''
+
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory, ensure_dir, BuildInterruptingException
+from multiprocessing import cpu_count
+from os.path import join
+import sh
+import shutil
+from os import environ
+from pythonforandroid.util import build_platform
+
+arch_to_sysroot = {'armeabi': 'arm', 'armeabi-v7a': 'arm', 'arm64-v8a': 'arm64'}
+
+
+def arch_to_toolchain(arch):
+ if 'arm' in arch.arch:
+ return arch.command_prefix
+ return arch.arch
+
+
+class LapackRecipe(Recipe):
+
+ name = 'lapack'
+ version = 'v3.10.1'
+ url = 'https://github.com/Reference-LAPACK/lapack/archive/{version}.tar.gz'
+ libdir = 'build/install/lib'
+ built_libraries = {'libblas.so': libdir, 'liblapack.so': libdir, 'libcblas.so': libdir}
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ ndk_dir = environ.get("LEGACY_NDK")
+ if ndk_dir is None:
+ raise BuildInterruptingException("Please set the environment variable 'LEGACY_NDK' to point to a NDK location with gcc/gfortran support (supported NDK version: 'r21e')")
+
+ GCC_VER = '4.9'
+ HOST = build_platform
+
+ sysroot_suffix = arch_to_sysroot.get(arch.arch, arch.arch)
+ sysroot = f"{ndk_dir}/platforms/{env['NDK_API']}/arch-{sysroot_suffix}"
+ FC = f"{ndk_dir}/toolchains/{arch_to_toolchain(arch)}-{GCC_VER}/prebuilt/{HOST}/bin/{arch.command_prefix}-gfortran"
+ env['FC'] = f'{FC} --sysroot={sysroot}'
+ if shutil.which(FC) is None:
+ raise BuildInterruptingException(f"{FC} not found. See https://github.com/mzakharo/android-gfortran")
+ return env
+
+ def build_arch(self, arch):
+ source_dir = self.get_build_dir(arch.arch)
+ build_target = join(source_dir, 'build')
+ install_target = join(build_target, 'install')
+
+ ensure_dir(build_target)
+ with current_directory(build_target):
+ env = self.get_recipe_env(arch)
+ ndk_dir = environ["LEGACY_NDK"]
+ shprint(sh.rm, '-rf', 'CMakeFiles/', 'CMakeCache.txt', _env=env)
+ opts = [
+ '-DCMAKE_SYSTEM_NAME=Android',
+ '-DCMAKE_POSITION_INDEPENDENT_CODE=1',
+ '-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch),
+ '-DCMAKE_ANDROID_NDK=' + ndk_dir,
+ '-DCMAKE_ANDROID_API={api}'.format(api=self.ctx.ndk_api),
+ '-DCMAKE_BUILD_TYPE=Release',
+ '-DCMAKE_INSTALL_PREFIX={}'.format(install_target),
+ '-DCBLAS=ON',
+ '-DBUILD_SHARED_LIBS=ON',
+ ]
+ if arch.arch == 'armeabi-v7a':
+ opts.append('-DCMAKE_ANDROID_ARM_NEON=ON')
+ shprint(sh.cmake, source_dir, *opts, _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = LapackRecipe()
diff --git a/p4a/pythonforandroid/recipes/leveldb/__init__.py b/p4a/pythonforandroid/recipes/leveldb/__init__.py
index e7ebe71..7f65a55 100644
--- a/p4a/pythonforandroid/recipes/leveldb/__init__.py
+++ b/p4a/pythonforandroid/recipes/leveldb/__init__.py
@@ -1,47 +1,46 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from pythonforandroid.recipe import Recipe
+from multiprocessing import cpu_count
from os.path import join
import sh
class LevelDBRecipe(Recipe):
- version = '1.18'
- url = 'https://github.com/google/leveldb/archive/v{version}.tar.gz'
- opt_depends = ['snappy']
- patches = ['disable-so-version.patch', 'find-snappy.patch']
-
- def should_build(self, arch):
- return not self.has_libs(arch, 'libleveldb.so', 'libgnustl_shared.so')
+ version = '1.22'
+ url = 'https://github.com/google/leveldb/archive/{version}.tar.gz'
+ depends = ['snappy']
+ built_libraries = {'libleveldb.so': '.'}
+ need_stl_shared = True
def build_arch(self, arch):
- super(LevelDBRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
- with current_directory(self.get_build_dir(arch.arch)):
- if 'snappy' in recipe.ctx.recipe_build_order:
- # Copy source from snappy recipe
- sh.cp('-rf', self.get_recipe('snappy', self.ctx).get_build_dir(arch.arch), 'snappy')
- # Build
- shprint(sh.make, _env=env)
- # Copy the shared library
- shutil.copyfile('libleveldb.so', join(self.ctx.get_libs_dir(arch.arch), 'libleveldb.so'))
- # Copy stl
- 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'))
+ source_dir = self.get_build_dir(arch.arch)
+ with current_directory(source_dir):
+ snappy_recipe = self.get_recipe('snappy', self.ctx)
+ snappy_build = snappy_recipe.get_build_dir(arch.arch)
- def get_recipe_env(self, arch):
- env = super(LevelDBRecipe, self).get_recipe_env(arch)
- env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE'
- if 'snappy' in recipe.ctx.recipe_build_order:
- env['CFLAGS'] += ' -DSNAPPY' + \
- ' -I./snappy'
- 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['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'
- return env
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DANDROID_STL=' + self.stl_lib_name,
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DCMAKE_BUILD_TYPE=Release',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ '-DHAVE_SNAPPY=1',
+ '-DCMAKE_CXX_FLAGS=-I{path}'.format(path=snappy_build),
+ '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lsnappy'.format(
+ path=snappy_build),
+ '-DCMAKE_EXE_LINKER_FLAGS=-L{path} -lsnappy'.format(
+ path=snappy_build),
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
recipe = LevelDBRecipe()
diff --git a/p4a/pythonforandroid/recipes/leveldb/disable-so-version.patch b/p4a/pythonforandroid/recipes/leveldb/disable-so-version.patch
deleted file mode 100644
index 0f6a7e7..0000000
--- a/p4a/pythonforandroid/recipes/leveldb/disable-so-version.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- leveldb/build_detect_platform 2014-09-16 23:19:52.000000000 +0200
-+++ leveldb-patch/build_detect_platform 2016-03-01 20:25:04.074484399 +0100
-@@ -124,6 +124,7 @@
- ;;
- OS_ANDROID_CROSSCOMPILE)
- PLATFORM=OS_ANDROID
-+ PLATFORM_SHARED_VERSIONED=
- COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX"
- PLATFORM_LDFLAGS="" # All pthread features are in the Android C library
- PORT_FILE=port/port_posix.cc
diff --git a/p4a/pythonforandroid/recipes/leveldb/find-snappy.patch b/p4a/pythonforandroid/recipes/leveldb/find-snappy.patch
deleted file mode 100644
index ae978d7..0000000
--- a/p4a/pythonforandroid/recipes/leveldb/find-snappy.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- leveldb/build_detect_platform 2014-09-16 23:19:52.000000000 +0200
-+++ leveldb-patch/build_detect_platform 2016-03-01 21:56:04.926650079 +0100
-@@ -156,7 +157,7 @@
- # except for the test and benchmark files. By default, find will output a list
- # of all files matching either rule, so we need to append -print to make the
- # prune take effect.
--DIRS="$PREFIX/db $PREFIX/util $PREFIX/table"
-+DIRS="$PREFIX/snappy $PREFIX/db $PREFIX/util $PREFIX/table"
-
- set -f # temporarily disable globbing so that our patterns aren't expanded
- PRUNE_TEST="-name *test*.cc -prune"
diff --git a/p4a/pythonforandroid/recipes/libbz2/__init__.py b/p4a/pythonforandroid/recipes/libbz2/__init__.py
new file mode 100644
index 0000000..01d5146
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libbz2/__init__.py
@@ -0,0 +1,57 @@
+import sh
+
+from multiprocessing import cpu_count
+
+from pythonforandroid.archs import Arch
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+
+
+class LibBz2Recipe(Recipe):
+
+ version = "1.0.8"
+ url = "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz"
+ built_libraries = {"libbz2.so": ""}
+ patches = ["lib_android.patch"]
+
+ def build_arch(self, arch: Arch) -> None:
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(
+ sh.make,
+ "-j",
+ str(cpu_count()),
+ f'CC={env["CC"]}',
+ "-f",
+ "Makefile-libbz2_so",
+ _env=env,
+ )
+
+ def get_library_includes(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-I` to link
+ with the bz2 lib. This string is usually added to the environment
+ variable `CPPFLAGS`.
+ """
+ return " -I" + self.get_build_dir(arch.arch)
+
+ def get_library_ldflags(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-L` to link
+ with the bz2 lib. This string is usually added to the environment
+ variable `LDFLAGS`.
+ """
+ return " -L" + self.get_build_dir(arch.arch)
+
+ @staticmethod
+ def get_library_libs_flag() -> str:
+ """
+ Returns a string with the appropriate `-l` flags to link with
+ the bz2 lib. This string is usually added to the environment
+ variable `LIBS`.
+ """
+ return " -lbz2"
+
+
+recipe = LibBz2Recipe()
diff --git a/p4a/pythonforandroid/recipes/libbz2/lib_android.patch b/p4a/pythonforandroid/recipes/libbz2/lib_android.patch
new file mode 100644
index 0000000..b208896
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libbz2/lib_android.patch
@@ -0,0 +1,29 @@
+Set default compiler to `clang` and disable versioned shared library
+--- bzip2-1.0.8/Makefile-libbz2_so.orig 2019-07-13 19:50:05.000000000 +0200
++++ bzip2-1.0.8/Makefile-libbz2_so 2020-03-13 20:10:32.336990786 +0100
+@@ -22,7 +22,7 @@
+
+
+ SHELL=/bin/sh
+-CC=gcc
++CC=clang
+ BIGFILES=-D_FILE_OFFSET_BITS=64
+ CFLAGS=-fpic -fPIC -Wall -Winline -O2 -g $(BIGFILES)
+
+@@ -35,13 +35,11 @@ OBJS= blocksort.o \
+ bzlib.o
+
+ all: $(OBJS)
+- $(CC) -shared -Wl,-soname -Wl,libbz2.so.1.0 -o libbz2.so.1.0.8 $(OBJS)
+- $(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so.1.0.8
+- rm -f libbz2.so.1.0
+- ln -s libbz2.so.1.0.8 libbz2.so.1.0
++ $(CC) -shared -Wl,-soname=libbz2.so -o libbz2.so $(OBJS)
++ $(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so
+
+ clean:
+- rm -f $(OBJS) bzip2.o libbz2.so.1.0.8 libbz2.so.1.0 bzip2-shared
++ rm -f $(OBJS) bzip2.o libbz2.so bzip2-shared
+
+ blocksort.o: blocksort.c
+ $(CC) $(CFLAGS) -c blocksort.c
diff --git a/p4a/pythonforandroid/recipes/libcurl/__init__.py b/p4a/pythonforandroid/recipes/libcurl/__init__.py
index e8cc860..2971532 100644
--- a/p4a/pythonforandroid/recipes/libcurl/__init__.py
+++ b/p4a/pythonforandroid/recipes/libcurl/__init__.py
@@ -1,40 +1,37 @@
import sh
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import 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'
+ built_libraries = {'libcurl.so': 'dist/lib'}
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)
+ openssl_recipe = self.get_recipe('openssl', self.ctx)
+ openssl_dir = openssl_recipe.get_build_dir(arch.arch)
+
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()
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',
+ '--host={}'.format(arch.command_prefix),
'--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
index ecf5265..614b0df 100644
--- a/p4a/pythonforandroid/recipes/libexpat/__init__.py
+++ b/p4a/pythonforandroid/recipes/libexpat/__init__.py
@@ -1,38 +1,32 @@
import sh
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import join
from multiprocessing import cpu_count
class LibexpatRecipe(Recipe):
version = 'master'
url = 'https://github.com/libexpat/libexpat/archive/{version}.zip'
+ built_libraries = {'libexpat.so': 'dist/lib'}
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',
+ '--host={}'.format(arch.command_prefix),
'--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 31ed9c6..767881b 100644
--- a/p4a/pythonforandroid/recipes/libffi/__init__.py
+++ b/p4a/pythonforandroid/recipes/libffi/__init__.py
@@ -2,7 +2,7 @@ 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, ensure_dir
+from pythonforandroid.util import current_directory
import sh
@@ -14,17 +14,12 @@ class LibffiRecipe(Recipe):
- `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'
+ version = 'v3.4.2'
+ url = 'https://github.com/libffi/libffi/archive/{version}.tar.gz'
- 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']
+ patches = ['remove-version-info.patch']
- def should_build(self, arch):
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so'))
+ built_libraries = {'libffi.so': '.libs'}
def build_arch(self, arch):
env = self.get_recipe_env(arch)
@@ -37,15 +32,8 @@ class LibffiRecipe(Recipe):
'--prefix=' + self.get_build_dir(arch.arch),
'--disable-builddir',
'--enable-shared', _env=env)
-
shprint(sh.make, '-j', str(cpu_count()), '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))
-
def get_include_dirs(self, arch):
return [join(self.get_build_dir(arch.arch), 'include')]
diff --git a/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch b/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch
deleted file mode 100644
index 0dc35c7..0000000
--- a/p4a/pythonforandroid/recipes/libffi/fix-includedir.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-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/libffi/remove-version-info.patch b/p4a/pythonforandroid/recipes/libffi/remove-version-info.patch
index 7bdc11a..0a32b7e 100644
--- a/p4a/pythonforandroid/recipes/libffi/remove-version-info.patch
+++ b/p4a/pythonforandroid/recipes/libffi/remove-version-info.patch
@@ -1,12 +1,11 @@
-diff -Naur libffi/Makefile.am b/Makefile.am
---- libffi/Makefile.am 2014-11-12 06:00:59.000000000 -0600
-+++ b/Makefile.am 2015-12-23 15:57:10.363148806 -0600
-@@ -249,7 +249,7 @@
- AM_CFLAGS += -DFFI_DEBUG
- endif
-
--libffi_la_LDFLAGS = -no-undefined -version-info `grep -v '^\#' $(srcdir)/libtool-version` $(LTLDFLAGS) $(AM_LTLDFLAGS)
+--- libffi/Makefile.am.orig 2018-12-21 16:11:26.159181262 +0100
++++ libffi/Makefile.am 2018-12-21 16:14:44.075179374 +0100
+@@ -156,7 +156,7 @@
+ libffi.map: $(top_srcdir)/libffi.map.in
+ $(COMPILE) -D$(TARGET) -E -x assembler-with-cpp -o $@ $<
+
+-libffi_la_LDFLAGS = -no-undefined $(libffi_version_info) $(libffi_version_script) $(LTLDFLAGS) $(AM_LTLDFLAGS)
+libffi_la_LDFLAGS = -no-undefined -avoid-version $(LTLDFLAGS) $(AM_LTLDFLAGS)
-
+ libffi_la_DEPENDENCIES = $(libffi_la_LIBADD) $(libffi_version_dep)
+
AM_CPPFLAGS = -I. -I$(top_srcdir)/include -Iinclude -I$(top_srcdir)/src
- AM_CCASFLAGS = $(AM_CPPFLAGS)
diff --git a/p4a/pythonforandroid/recipes/libgeos/__init__.py b/p4a/pythonforandroid/recipes/libgeos/__init__.py
index 30786f8..cff9fe0 100644
--- a/p4a/pythonforandroid/recipes/libgeos/__init__.py
+++ b/p4a/pythonforandroid/recipes/libgeos/__init__.py
@@ -1,44 +1,52 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
-import sh
+from pythonforandroid.util import current_directory, ensure_dir
+from pythonforandroid.toolchain import shprint
+from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
+from os.path import join
+import sh
class LibgeosRecipe(Recipe):
- version = '3.5'
- # url = 'http://download.osgeo.org/geos/geos-{version}.tar.bz2'
- url = 'https://github.com/libgeos/libgeos/archive/svn-{version}.zip'
+ version = '3.7.1'
+ url = 'https://github.com/libgeos/libgeos/archive/{version}.zip'
depends = []
-
- def should_build(self, arch):
- super(LibgeosRecipe, self).should_build(arch)
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libgeos_c.so'))
+ built_libraries = {
+ 'libgeos.so': 'install_target/lib',
+ 'libgeos_c.so': 'install_target/lib'
+ }
+ need_stl_shared = True
def build_arch(self, arch):
- super(LibgeosRecipe, self).build_arch(arch)
- env = self.get_recipe_env(arch)
+ source_dir = self.get_build_dir(arch.arch)
+ build_target = join(source_dir, 'build_target')
+ install_target = join(source_dir, 'install_target')
- with current_directory(self.get_build_dir(arch.arch)):
- 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)
+ ensure_dir(build_target)
+ with current_directory(build_target):
+ env = self.get_recipe_env(arch)
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DANDROID_STL=' + self.stl_lib_name,
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DCMAKE_INSTALL_PREFIX={}'.format(install_target),
+ '-DCMAKE_BUILD_TYPE=Release',
+
+ '-DGEOS_ENABLE_TESTS=OFF',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+
+ # We make the install because this way we will have all the
+ # includes in one place (mostly we are interested in `geos_c.h`,
+ # which is not in the include folder, so this way we make easier to
+ # link with this library...case of shapely's recipe)
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)
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}/include'.format(
- self.ctx.ndk_dir, arch)
- env['CXXFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}'.format(
- self.ctx.ndk_dir, arch)
- env['CXXFLAGS'] += ' -lgnustl_shared'
- env['LDFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}'.format(
- self.ctx.ndk_dir, arch)
- return env
recipe = LibgeosRecipe()
diff --git a/p4a/pythonforandroid/recipes/libglob/__init__.py b/p4a/pythonforandroid/recipes/libglob/__init__.py
index e0fccfe..f63db42 100644
--- a/p4a/pythonforandroid/recipes/libglob/__init__.py
+++ b/p4a/pythonforandroid/recipes/libglob/__init__.py
@@ -3,13 +3,13 @@
available via '-lglob' LDFLAG
"""
from os.path import exists, join
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import current_directory
from pythonforandroid.logger import info, shprint
import sh
-class LibGlobRecipe(CompiledComponentsPythonRecipe):
+class LibGlobRecipe(Recipe):
"""Make a glob.h and glob.so for the python_install_dir()"""
version = '0.0.1'
url = None
@@ -20,8 +20,9 @@ class LibGlobRecipe(CompiledComponentsPythonRecipe):
# https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.c
# and pushed in via patch
name = 'libglob'
+ built_libraries = {'libglob.so': '.'}
- depends = [('hostpython2', 'hostpython3')]
+ depends = ['hostpython3']
patches = ['glob.patch']
def should_build(self, arch):
@@ -60,7 +61,6 @@ class LibGlobRecipe(CompiledComponentsPythonRecipe):
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
index c7fe817..ee71719 100644
--- a/p4a/pythonforandroid/recipes/libglob/glob.patch
+++ b/p4a/pythonforandroid/recipes/libglob/glob.patch
@@ -911,7 +911,7 @@ diff -Nur /tmp/x/glob.c libglob/glob.c
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 @@
+@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
@@ -952,10 +952,12 @@ diff -Nur /tmp/x/glob.h libglob/glob.h
+
+#include
+#include
++#ifndef ARG_MAX
+#define ARG_MAX 6553
++#endif
+
+#ifndef _SIZE_T_DECLARED
-+typedef __size_t size_t;
++#include
+#define _SIZE_T_DECLARED
+#endif
+
diff --git a/p4a/pythonforandroid/recipes/libiconv/__init__.py b/p4a/pythonforandroid/recipes/libiconv/__init__.py
index 4a64669..1cdcb91 100644
--- a/p4a/pythonforandroid/recipes/libiconv/__init__.py
+++ b/p4a/pythonforandroid/recipes/libiconv/__init__.py
@@ -1,5 +1,5 @@
-import os
-from pythonforandroid.toolchain import shprint, current_directory
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
import sh
@@ -7,28 +7,21 @@ import sh
class LibIconvRecipe(Recipe):
- version = '1.15'
+ version = '1.16'
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'))
+ built_libraries = {'libiconv.so': 'lib/.libs'}
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(),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
_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
deleted file mode 100644
index 5bc20b3..0000000
--- a/p4a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-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/liblzma/__init__.py b/p4a/pythonforandroid/recipes/liblzma/__init__.py
new file mode 100644
index 0000000..0b880bc
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/liblzma/__init__.py
@@ -0,0 +1,77 @@
+import sh
+
+from multiprocessing import cpu_count
+from os.path import exists, join
+
+from pythonforandroid.archs import Arch
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+
+
+class LibLzmaRecipe(Recipe):
+
+ version = '5.2.4'
+ url = 'https://tukaani.org/xz/xz-{version}.tar.gz'
+ built_libraries = {'liblzma.so': 'p4a_install/lib'}
+
+ def build_arch(self, arch: Arch) -> None:
+ env = self.get_recipe_env(arch)
+ install_dir = join(self.get_build_dir(arch.arch), 'p4a_install')
+ 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=' + install_dir,
+ '--disable-builddir',
+ '--disable-static',
+ '--enable-shared',
+
+ '--disable-xz',
+ '--disable-xzdec',
+ '--disable-lzmadec',
+ '--disable-lzmainfo',
+ '--disable-scripts',
+ '--disable-doc',
+
+ _env=env)
+ shprint(
+ sh.make, '-j', str(cpu_count()),
+ _env=env
+ )
+
+ shprint(sh.make, 'install', _env=env)
+
+ def get_library_includes(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-I` to link
+ with the lzma lib. This string is usually added to the environment
+ variable `CPPFLAGS`.
+ """
+ return " -I" + join(
+ self.get_build_dir(arch.arch), 'p4a_install', 'include',
+ )
+
+ def get_library_ldflags(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-L` to link
+ with the lzma lib. This string is usually added to the environment
+ variable `LDFLAGS`.
+ """
+ return " -L" + join(
+ self.get_build_dir(arch.arch), self.built_libraries['liblzma.so'],
+ )
+
+ @staticmethod
+ def get_library_libs_flag() -> str:
+ """
+ Returns a string with the appropriate `-l` flags to link with
+ the lzma lib. This string is usually added to the environment
+ variable `LIBS`.
+ """
+ return " -llzma"
+
+
+recipe = LibLzmaRecipe()
diff --git a/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py b/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py
index 9235ad4..31ebd3c 100644
--- a/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py
+++ b/p4a/pythonforandroid/recipes/libmysqlclient/__init__.py
@@ -38,7 +38,7 @@ class LibmysqlclientRecipe(Recipe):
self.install_libs(arch, join('libmysql', 'libmysql.so'))
# def get_recipe_env(self, arch=None):
- # env = super(LibmysqlclientRecipe, self).get_recipe_env(arch)
+ # env = super().get_recipe_env(arch)
# env['WITHOUT_SERVER'] = 'ON'
# ncurses = self.get_recipe('ncurses', self)
# # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch),
diff --git a/p4a/pythonforandroid/recipes/libnacl/__init__.py b/p4a/pythonforandroid/recipes/libnacl/__init__.py
deleted file mode 100644
index 3fc5da8..0000000
--- a/p4a/pythonforandroid/recipes/libnacl/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-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', '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
index 064189e..875dd7f 100644
--- a/p4a/pythonforandroid/recipes/libogg/__init__.py
+++ b/p4a/pythonforandroid/recipes/libogg/__init__.py
@@ -1,26 +1,22 @@
-from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import current_directory, shprint
-from os.path import join
import sh
-class OggRecipe(NDKRecipe):
+class OggRecipe(Recipe):
version = '1.3.3'
url = 'http://downloads.xiph.org/releases/ogg/libogg-{version}.tar.gz'
-
- generated_libraries = ['libogg.so']
+ built_libraries = {'libogg.so': 'src/.libs'}
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,
+ '--host=' + arch.command_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/libpcre/__init__.py b/p4a/pythonforandroid/recipes/libpcre/__init__.py
new file mode 100644
index 0000000..ddf005e
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libpcre/__init__.py
@@ -0,0 +1,31 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+import sh
+from multiprocessing import cpu_count
+from os.path import join
+
+
+class LibpcreRecipe(Recipe):
+ version = '8.44'
+ url = 'https://ftp.pcre.org/pub/pcre/pcre-{version}.tar.bz2'
+
+ built_libraries = {'libpcre.so': '.libs'}
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(
+ sh.Command('./configure'),
+ *'''--host=arm-linux-androideabi
+ --disable-cpp --enable-jit --enable-utf8
+ --enable-unicode-properties'''.split(),
+ _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+
+ def get_lib_dir(self, arch):
+ return join(self.get_build_dir(arch), '.libs')
+
+
+recipe = LibpcreRecipe()
diff --git a/p4a/pythonforandroid/recipes/libpq/__init__.py b/p4a/pythonforandroid/recipes/libpq/__init__.py
index 45c296a..1faed7c 100644
--- a/p4a/pythonforandroid/recipes/libpq/__init__.py
+++ b/p4a/pythonforandroid/recipes/libpq/__init__.py
@@ -4,10 +4,16 @@ import os.path
class LibpqRecipe(Recipe):
- version = '9.5.3'
+ version = '10.12'
url = 'http://ftp.postgresql.org/pub/source/v{version}/postgresql-{version}.tar.bz2'
depends = []
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['USE_DEV_URANDOM'] = '1'
+
+ return env
+
def should_build(self, arch):
return not os.path.isfile('{}/libpq.a'.format(self.ctx.get_libs_dir(arch.arch)))
diff --git a/p4a/pythonforandroid/recipes/librt/__init__.py b/p4a/pythonforandroid/recipes/librt/__init__.py
index 9eb56b3..6c42490 100644
--- a/p4a/pythonforandroid/recipes/librt/__init__.py
+++ b/p4a/pythonforandroid/recipes/librt/__init__.py
@@ -18,11 +18,8 @@ class LibRt(Recipe):
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):
+ libc_path = join(arch.ndk_lib_dir_versioned, 'libc')
# 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),
@@ -35,13 +32,13 @@ class LibRt(Recipe):
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',
+ 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',
+ libc_path + '.a',
join(fake_librt_temp_folder, "librt.a"),
)
diff --git a/p4a/pythonforandroid/recipes/libsecp256k1/__init__.py b/p4a/pythonforandroid/recipes/libsecp256k1/__init__.py
index a855257..f3a2772 100644
--- a/p4a/pythonforandroid/recipes/libsecp256k1/__init__.py
+++ b/p4a/pythonforandroid/recipes/libsecp256k1/__init__.py
@@ -1,4 +1,5 @@
-from pythonforandroid.toolchain import shprint, current_directory
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
from os.path import exists
@@ -7,26 +8,25 @@ import sh
class LibSecp256k1Recipe(Recipe):
+ built_libraries = {'libsecp256k1.so': '.libs'}
+
url = 'https://github.com/bitcoin-core/secp256k1/archive/master.zip'
def build_arch(self, arch):
- super(LibSecp256k1Recipe, 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('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
'--enable-shared',
'--enable-module-recovery',
'--enable-experimental',
'--enable-module-ecdh',
_env=env)
shprint(sh.make, '-j' + str(cpu_count()), _env=env)
- libs = ['.libs/libsecp256k1.so']
- self.install_libs(arch, *libs)
recipe = LibSecp256k1Recipe()
diff --git a/p4a/pythonforandroid/recipes/libshine/__init__.py b/p4a/pythonforandroid/recipes/libshine/__init__.py
index fe9b5b5..32fa9e1 100644
--- a/p4a/pythonforandroid/recipes/libshine/__init__.py
+++ b/p4a/pythonforandroid/recipes/libshine/__init__.py
@@ -1,5 +1,8 @@
-from pythonforandroid.toolchain import Recipe, current_directory, shprint
-from os.path import exists, join, realpath
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
+from os.path import realpath
import sh
@@ -7,9 +10,15 @@ class LibShineRecipe(Recipe):
version = 'c72aba9031bde18a0995e7c01c9b53f2e08a0e46'
url = 'https://github.com/toots/shine/archive/{version}.zip'
- def should_build(self, arch):
- build_dir = self.get_build_dir(arch.arch)
- return not exists(join(build_dir, 'lib', 'libshine.a'))
+ built_libraries = {'libshine.so': 'lib'}
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ # technically, libraries should go to `LDLIBS`, but it seems
+ # that libshine doesn't like so, and it will fail on linking stage
+ env['LDLIBS'] = env['LDLIBS'].replace(' -lm', '')
+ env['LDFLAGS'] += ' -lm'
+ return env
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
@@ -17,13 +26,13 @@ class LibShineRecipe(Recipe):
shprint(sh.Command('./bootstrap'))
configure = sh.Command('./configure')
shprint(configure,
- '--host=arm-linux',
+ f'--host={arch.command_prefix}',
'--enable-pic',
- '--disable-shared',
- '--enable-static',
- '--prefix={}'.format(realpath('.')),
+ '--disable-static',
+ '--enable-shared',
+ f'--prefix={realpath(".")}',
_env=env)
- shprint(sh.make, '-j4', _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
diff --git a/p4a/pythonforandroid/recipes/libsodium/__init__.py b/p4a/pythonforandroid/recipes/libsodium/__init__.py
index 9911e36..f66fc18 100644
--- a/p4a/pythonforandroid/recipes/libsodium/__init__.py
+++ b/p4a/pythonforandroid/recipes/libsodium/__init__.py
@@ -1,5 +1,7 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
import sh
@@ -8,22 +10,24 @@ class LibsodiumRecipe(Recipe):
url = 'https://github.com/jedisct1/libsodium/releases/download/{version}/libsodium-{version}.tar.gz'
depends = []
patches = ['size_max_fix.patch']
-
- def should_build(self, arch):
- super(LibsodiumRecipe, self).should_build(arch)
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libsodium.so'))
+ built_libraries = {'libsodium.so': 'src/libsodium/.libs'}
def build_arch(self, arch):
- super(LibsodiumRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
bash = sh.Command('bash')
- 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'))
+ shprint(
+ bash,
+ 'configure',
+ '--disable-soname-versions',
+ '--host={}'.format(arch.command_prefix),
+ '--enable-shared',
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
def get_recipe_env(self, arch):
- env = super(LibsodiumRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['CFLAGS'] += ' -Os'
return env
diff --git a/p4a/pythonforandroid/recipes/libtorrent/__init__.py b/p4a/pythonforandroid/recipes/libtorrent/__init__.py
index c73bb02..24f9408 100644
--- a/p4a/pythonforandroid/recipes/libtorrent/__init__.py
+++ b/p4a/pythonforandroid/recipes/libtorrent/__init__.py
@@ -5,8 +5,8 @@ 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
+# 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'):
@@ -24,7 +24,8 @@ def get_lib_from(search_directory, lib_extension='.so'):
class LibtorrentRecipe(Recipe):
# Todo: make recipe compatible with all p4a architectures
'''
- .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
+ .. note:: This recipe can be built only against API 21+ and an android
+ ndk >= r19
.. versionchanged:: 0.6.0
Rewrote recipe to support clang's build and boost 1.68. The following
@@ -33,9 +34,14 @@ class LibtorrentRecipe(Recipe):
- Bumped version number to 1.2.0
- added python 3 compatibility
- new system to detect/copy generated libraries
+
+ .. versionchanged:: 2019.08.09.1.dev0
+
+ - Bumped version number to 1.2.1
+ - Adapted to work with ndk-r19+
'''
- version = '1_2_0'
- url = 'https://github.com/arvidn/libtorrent/archive/libtorrent_{version}.tar.gz'
+ version = '1_2_1'
+ url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-{version}.tar.gz'
depends = ['boost']
opt_depends = ['openssl']
@@ -55,14 +61,14 @@ class LibtorrentRecipe(Recipe):
self.ctx.has_package('libtorrent', arch.arch))
def prebuild_arch(self, arch):
- super(LibtorrentRecipe, self).prebuild_arch(arch)
+ super().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)
def build_arch(self, arch):
- super(LibtorrentRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
env['PYTHON_HOST'] = self.ctx.hostpython
@@ -76,7 +82,7 @@ class LibtorrentRecipe(Recipe):
'-j' + str(cpu_count()),
'--debug-configuration', # so we know if our python is detected
# '--deprecated-functions=off',
- 'toolset=clang-arm',
+ 'toolset=clang-{arch}'.format(arch=env['ARCH']),
'abi=aapcs',
'binary-format=elf',
'cxxflags=-std=c++11',
@@ -105,8 +111,12 @@ class LibtorrentRecipe(Recipe):
# 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)
+ b2_build_dir = (
+ 'build/clang-linux-{arch}/release/{encryption}/'
+ 'lt-visibility-hidden/'.format(
+ arch=env['ARCH'], 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))
@@ -122,7 +132,7 @@ class LibtorrentRecipe(Recipe):
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'))
+ join(self.ctx.get_site_packages_dir(arch), 'libtorrent.so'))
def get_recipe_env(self, arch):
# Use environment from boost recipe, cause we use b2 tool from boost
diff --git a/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch b/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
index 183705c..4b688be 100644
--- a/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
+++ b/p4a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
@@ -15,6 +15,6 @@
setup(
- name='python-libtorrent',
+ name='libtorrent',
- version='1.2.0',
+ version='1.2.1',
author='Arvid Norberg',
author_email='arvid@libtorrent.org',
diff --git a/p4a/pythonforandroid/recipes/libvorbis/__init__.py b/p4a/pythonforandroid/recipes/libvorbis/__init__.py
index 87c7a44..bbbca6f 100644
--- a/p4a/pythonforandroid/recipes/libvorbis/__init__.py
+++ b/p4a/pythonforandroid/recipes/libvorbis/__init__.py
@@ -12,7 +12,7 @@ class VorbisRecipe(NDKRecipe):
generated_libraries = ['libvorbis.so', 'libvorbisfile.so', 'libvorbisenc.so']
def get_recipe_env(self, arch=None):
- env = super(VorbisRecipe, self).get_recipe_env(arch)
+ env = super().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
@@ -21,8 +21,7 @@ class VorbisRecipe(NDKRecipe):
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,
+ '--host=' + arch.command_prefix,
]
configure = sh.Command('./configure')
shprint(configure, *flags, _env=env)
diff --git a/p4a/pythonforandroid/recipes/libvpx/__init__.py b/p4a/pythonforandroid/recipes/libvpx/__init__.py
new file mode 100644
index 0000000..0173e36
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libvpx/__init__.py
@@ -0,0 +1,59 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import current_directory, shprint
+from os.path import join, realpath
+from multiprocessing import cpu_count
+import sh
+
+
+TARGETS = {
+ 'armeabi-v7a': 'armv7-android-gcc',
+ 'arm64-v8a': 'arm64-android-gcc',
+ 'x86': 'x86-android-gcc',
+ 'x86_64': 'x86_64-android-gcc',
+}
+
+
+class VPXRecipe(Recipe):
+ version = '1.11.0'
+ url = 'https://github.com/webmproject/libvpx/archive/v{version}.tar.gz'
+
+ patches = [
+ # See https://git.io/Jq50q
+ join('patches', '0001-android-force-neon-runtime.patch'),
+ ]
+
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ env['CXXFLAGS'] += f' -I{self.ctx.ndk.libcxx_include_dir}'
+ return env
+
+ def build_arch(self, arch):
+ with current_directory(self.get_build_dir(arch.arch)):
+ env = self.get_recipe_env(arch)
+ flags = [
+ '--target=' + TARGETS[arch.arch],
+ '--enable-pic',
+ '--enable-vp8',
+ '--enable-vp9',
+ '--enable-static',
+ '--enable-small',
+ '--disable-shared',
+ '--disable-examples',
+ '--disable-unit-tests',
+ '--disable-tools',
+ '--disable-docs',
+ '--disable-install-docs',
+ '--disable-realtime-only',
+ f'--prefix={realpath(".")}',
+ ]
+
+ if arch.arch == 'armeabi-v7a':
+ flags.append('--disable-neon-asm')
+
+ configure = sh.Command('./configure')
+ shprint(configure, *flags, _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = VPXRecipe()
diff --git a/p4a/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch b/p4a/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch
new file mode 100644
index 0000000..220800d
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch
@@ -0,0 +1,25 @@
+diff -u -r ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c ./vpx_ports/arm_cpudetect.c
+--- ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c 2017-01-12 21:27:27.000000000 +0100
++++ ./vpx_ports/arm_cpudetect.c 2017-01-29 23:55:05.399283897 +0100
+@@ -92,20 +92,17 @@
+ }
+
+ #elif defined(__ANDROID__) /* end _MSC_VER */
+-#include
+
+ int arm_cpu_caps(void) {
+ int flags;
+ int mask;
+- uint64_t features;
+ if (!arm_cpu_env_flags(&flags)) {
+ return flags;
+ }
+ mask = arm_cpu_env_mask();
+- features = android_getCpuFeatures();
+
+ #if HAVE_NEON || HAVE_NEON_ASM
+- if (features & ANDROID_CPU_ARM_FEATURE_NEON) flags |= HAS_NEON;
++ flags |= HAS_NEON;
+ #endif /* HAVE_NEON || HAVE_NEON_ASM */
+ return flags & mask;
+ }
diff --git a/p4a/pythonforandroid/recipes/libwebp/__init__.py b/p4a/pythonforandroid/recipes/libwebp/__init__.py
new file mode 100644
index 0000000..aacd485
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/libwebp/__init__.py
@@ -0,0 +1,50 @@
+from multiprocessing import cpu_count
+from os.path import join
+
+import sh
+
+from pythonforandroid.util import current_directory, ensure_dir
+from pythonforandroid.toolchain import shprint
+from pythonforandroid.recipe import Recipe
+
+
+class LibwebpRecipe(Recipe):
+ version = '1.1.0'
+ url = 'https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-{version}.tar.gz' # noqa
+ depends = []
+ built_libraries = {
+ 'libwebp.so': 'installation/lib',
+ 'libwebpdecoder.so': 'installation/lib',
+ 'libwebpdemux.so': 'installation/lib',
+ 'libwebpmux.so': 'installation/lib',
+ }
+
+ def build_arch(self, arch):
+ source_dir = self.get_build_dir(arch.arch)
+ build_dir = join(source_dir, 'build')
+ install_dir = join(source_dir, 'installation')
+ toolchain_file = join(
+ self.ctx.ndk_dir, 'build', 'cmake', 'android.toolchain.cmake',
+ )
+
+ ensure_dir(build_dir)
+ with current_directory(build_dir):
+ env = self.get_recipe_env(arch)
+ shprint(sh.cmake, source_dir,
+ f'-DANDROID_ABI={arch.arch}',
+ f'-DANDROID_NATIVE_API_LEVEL={self.ctx.ndk_api}',
+
+ f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}',
+ f'-DCMAKE_INSTALL_PREFIX={install_dir}',
+ '-DCMAKE_BUILD_TYPE=Release',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+ # We make the install because this way we will have
+ # all the includes and libraries in one place
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = LibwebpRecipe()
diff --git a/p4a/pythonforandroid/recipes/libx264/__init__.py b/p4a/pythonforandroid/recipes/libx264/__init__.py
index c139b4c..6341309 100644
--- a/p4a/pythonforandroid/recipes/libx264/__init__.py
+++ b/p4a/pythonforandroid/recipes/libx264/__init__.py
@@ -1,31 +1,29 @@
-from pythonforandroid.toolchain import Recipe, current_directory, shprint
-from os.path import exists, join, realpath
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
+from os.path import realpath
import sh
class LibX264Recipe(Recipe):
- 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'
-
- def should_build(self, arch):
- build_dir = self.get_build_dir(arch.arch)
- return not exists(join(build_dir, 'lib', 'libx264.a'))
+ version = '5db6aa6cab1b146e07b60cc1736a01f21da01154' # commit of latest known stable version
+ url = 'https://code.videolan.org/videolan/x264/-/archive/{version}/x264-{version}.zip'
+ built_libraries = {'libx264.a': 'lib'}
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = self.get_recipe_env(arch)
configure = sh.Command('./configure')
shprint(configure,
- '--cross-prefix=arm-linux-androideabi-',
- '--host=arm-linux',
+ f'--host={arch.command_prefix}',
'--disable-asm',
'--disable-cli',
'--enable-pic',
- '--disable-shared',
'--enable-static',
'--prefix={}'.format(realpath('.')),
_env=env)
- shprint(sh.make, '-j4', _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
diff --git a/p4a/pythonforandroid/recipes/libxml2/__init__.py b/p4a/pythonforandroid/recipes/libxml2/__init__.py
index cdeaf88..100c528 100644
--- a/p4a/pythonforandroid/recipes/libxml2/__init__.py
+++ b/p4a/pythonforandroid/recipes/libxml2/__init__.py
@@ -1,22 +1,18 @@
from pythonforandroid.recipe import Recipe
-from pythonforandroid.toolchain import shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import exists
import sh
class Libxml2Recipe(Recipe):
- version = '2.9.8'
+ version = '2.9.12'
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'))
+ built_libraries = {'libxml2.a': '.libs'}
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)):
@@ -46,11 +42,8 @@ class Libxml2Recipe(Recipe):
# 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 = super().get_recipe_env(arch)
env['CONFIG_SHELL'] = '/bin/bash'
env['SHELL'] = '/bin/bash'
env['CC'] += ' -I' + self.get_build_dir(arch.arch)
diff --git a/p4a/pythonforandroid/recipes/libxslt/__init__.py b/p4a/pythonforandroid/recipes/libxslt/__init__.py
index 076d6cc..d9127cf 100644
--- a/p4a/pythonforandroid/recipes/libxslt/__init__.py
+++ b/p4a/pythonforandroid/recipes/libxslt/__init__.py
@@ -1,24 +1,23 @@
from pythonforandroid.recipe import Recipe
-from pythonforandroid.toolchain import shprint, shutil, current_directory
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
from os.path import exists, join
import sh
class LibxsltRecipe(Recipe):
- version = '1.1.32'
+ version = '1.1.34'
url = 'http://xmlsoft.org/sources/libxslt-{version}.tar.gz'
depends = ['libxml2']
patches = ['fix-dlopen.patch']
+ built_libraries = {
+ 'libxslt.a': 'libxslt/.libs',
+ 'libexslt.a': 'libexslt/.libs'
+ }
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):
@@ -45,13 +44,10 @@ class LibxsltRecipe(Recipe):
_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'))
+ shprint(sh.Command('chmod'), '+x', 'xslt-config')
def get_recipe_env(self, arch):
- env = super(LibxsltRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['CONFIG_SHELL'] = '/bin/bash'
env['SHELL'] = '/bin/bash'
diff --git a/p4a/pythonforandroid/recipes/libzbar/__init__.py b/p4a/pythonforandroid/recipes/libzbar/__init__.py
index 43ae34c..4e26ca4 100644
--- a/p4a/pythonforandroid/recipes/libzbar/__init__.py
+++ b/p4a/pythonforandroid/recipes/libzbar/__init__.py
@@ -1,6 +1,7 @@
import os
-from pythonforandroid.toolchain import shprint, current_directory
from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
from multiprocessing import cpu_count
import sh
@@ -15,12 +16,10 @@ class LibZBarRecipe(Recipe):
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'))
+ built_libraries = {'libzbar.so': 'zbar/.libs'}
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(LibZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().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')
@@ -28,15 +27,14 @@ class LibZBarRecipe(Recipe):
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(),
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
# Python bindings are compiled in a separated recipe
'--with-python=no',
'--with-gtk=no',
@@ -50,8 +48,6 @@ class LibZBarRecipe(Recipe):
'--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/libzmq/__init__.py b/p4a/pythonforandroid/recipes/libzmq/__init__.py
index b33f3ac..243517b 100644
--- a/p4a/pythonforandroid/recipes/libzmq/__init__.py
+++ b/p4a/pythonforandroid/recipes/libzmq/__init__.py
@@ -1,21 +1,18 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from pythonforandroid.util import ensure_dir
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from os.path import join
import sh
class LibZMQRecipe(Recipe):
- version = '4.1.4'
- url = 'http://download.zeromq.org/zeromq-{version}.tar.gz'
+ version = '4.3.2'
+ url = 'https://github.com/zeromq/libzmq/releases/download/v{version}/zeromq-{version}.zip'
depends = []
-
- def should_build(self, arch):
- super(LibZMQRecipe, self).should_build(arch)
- return True
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libzmq.so'))
+ built_libraries = {'libzmq.so': 'src/.libs'}
+ need_stl_shared = True
def build_arch(self, arch):
- super(LibZMQRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
#
# libsodium_recipe = Recipe.get_recipe('libsodium', self.ctx)
@@ -27,49 +24,19 @@ class LibZMQRecipe(Recipe):
curdir = self.get_build_dir(arch.arch)
prefix = join(curdir, "install")
+
with current_directory(curdir):
bash = sh.Command('sh')
shprint(
bash, './configure',
- '--host=arm-linux-androideabi',
+ '--host={}'.format(arch.command_prefix),
'--without-documentation',
'--prefix={}'.format(prefix),
'--with-libsodium=no',
+ '--disable-libunwind',
_env=env)
shprint(sh.make, _env=env)
shprint(sh.make, 'install', _env=env)
- shutil.copyfile('.libs/libzmq.so', join(
- self.ctx.get_libs_dir(arch.arch), 'libzmq.so'))
-
- bootstrap_obj_dir = join(self.ctx.bootstrap.build_dir, 'obj', 'local', arch.arch)
- ensure_dir(bootstrap_obj_dir)
- shutil.copyfile(
- '{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}/libgnustl_shared.so'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch),
- join(bootstrap_obj_dir, 'libgnustl_shared.so'))
-
- # 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),
- self.ctx.get_libs_dir(arch.arch)
- )
-
- def get_recipe_env(self, arch):
- # XXX should stl be configuration for the toolchain itself?
- env = super(LibZMQRecipe, self).get_recipe_env(arch)
- env['CFLAGS'] += ' -Os'
- env['CXXFLAGS'] += ' -Os -fPIC -fvisibility=default'
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/{}/include'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version)
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}/include'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- env['CXXFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- env['CXXFLAGS'] += ' -lgnustl_shared'
- env['LDFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- return env
recipe = LibZMQRecipe()
diff --git a/p4a/pythonforandroid/recipes/lxml/__init__.py b/p4a/pythonforandroid/recipes/lxml/__init__.py
index 6d4b91c..4910caf 100644
--- a/p4a/pythonforandroid/recipes/lxml/__init__.py
+++ b/p4a/pythonforandroid/recipes/lxml/__init__.py
@@ -4,7 +4,7 @@ from os import uname
class LXMLRecipe(CompiledComponentsPythonRecipe):
- version = '4.2.5'
+ version = '4.8.0'
url = 'https://pypi.python.org/packages/source/l/lxml/lxml-{version}.tar.gz' # noqa
depends = ['librt', 'libxml2', 'libxslt', 'setuptools']
name = 'lxml'
@@ -12,53 +12,52 @@ class LXMLRecipe(CompiledComponentsPythonRecipe):
call_hostpython_via_targetpython = False # Due to setuptools
def should_build(self, arch):
- super(LXMLRecipe, self).should_build(arch)
+ super().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']
+ 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)
+ env = super().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'
+ env["STATIC"] = "true"
- # 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'
+ env["LXML_STATIC_INCLUDE_DIRS"] = "{}:{}".format(
+ join(libxml2_build_dir, "include"), join(libxslt_build_dir)
+ )
+ env["LXML_STATIC_LIBRARY_DIRS"] = "{}:{}:{}".format(
+ join(libxml2_build_dir, ".libs"),
+ join(libxslt_build_dir, "libxslt", ".libs"),
+ join(libxslt_build_dir, "libexslt", ".libs"),
+ )
- if cflags not in env['CFLAGS']:
- env['CFLAGS'] += cflags
+ env["WITH_XML2_CONFIG"] = join(libxml2_build_dir, "xml2-config")
+ env["WITH_XSLT_CONFIG"] = join(libxslt_build_dir, "xslt-config")
+
+ env["LXML_STATIC_BINARIES"] = "{}:{}:{}".format(
+ join(libxml2_build_dir, ".libs", "libxml2.a"),
+ join(libxslt_build_dir, "libxslt", ".libs", "libxslt.a"),
+ join(libxslt_build_dir, "libexslt", ".libs", "libexslt.a"),
+ )
return env
diff --git a/p4a/pythonforandroid/recipes/m2crypto/__init__.py b/p4a/pythonforandroid/recipes/m2crypto/__init__.py
index 653eeca..5786049 100644
--- a/p4a/pythonforandroid/recipes/m2crypto/__init__.py
+++ b/p4a/pythonforandroid/recipes/m2crypto/__init__.py
@@ -32,7 +32,7 @@ class M2CryptoRecipe(CompiledComponentsPythonRecipe):
env['STRIP'], '{}', ';', _env=env)
def get_recipe_env(self, arch):
- env = super(M2CryptoRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['OPENSSL_BUILD_PATH'] = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
return env
diff --git a/p4a/pythonforandroid/recipes/matplotlib/__init__.py b/p4a/pythonforandroid/recipes/matplotlib/__init__.py
new file mode 100644
index 0000000..f79cde3
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/matplotlib/__init__.py
@@ -0,0 +1,94 @@
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+from pythonforandroid.util import ensure_dir
+
+from os.path import join
+import shutil
+
+
+class MatplotlibRecipe(CppCompiledComponentsPythonRecipe):
+
+ version = '3.5.2'
+ url = 'https://github.com/matplotlib/matplotlib/archive/v{version}.zip'
+
+ depends = ['kiwisolver', 'numpy', 'pillow', 'setuptools', 'freetype']
+
+ python_depends = ['cycler', 'fonttools', 'packaging', 'pyparsing', 'python-dateutil']
+
+ def generate_libraries_pc_files(self, arch):
+ """
+ Create *.pc files for libraries that `matplotib` depends on.
+
+ Because, for unix platforms, the mpl install script uses `pkg-config`
+ to detect libraries installed in non standard locations (our case...
+ well...we don't even install the libraries...so we must trick a little
+ the mlp install).
+ """
+ pkg_config_path = self.get_recipe_env(arch)['PKG_CONFIG_PATH']
+ ensure_dir(pkg_config_path)
+
+ lib_to_pc_file = {
+ # `pkg-config` search for version freetype2.pc, our current
+ # version for freetype, but we have our recipe named without
+ # the version...so we add it in here for our pc file
+ 'freetype': 'freetype2.pc',
+ }
+
+ for lib_name in {'freetype'}:
+ pc_template_file = join(
+ self.get_recipe_dir(),
+ f'lib{lib_name}.pc.template'
+ )
+ # read template file into buffer
+ with open(pc_template_file) as template_file:
+ text_buffer = template_file.read()
+ # set the library absolute path and library version
+ lib_recipe = self.get_recipe(lib_name, self.ctx)
+ text_buffer = text_buffer.replace(
+ 'path_to_built', lib_recipe.get_build_dir(arch.arch),
+ )
+ text_buffer = text_buffer.replace(
+ 'library_version', lib_recipe.version,
+ )
+
+ # write the library pc file into our defined dir `PKG_CONFIG_PATH`
+ pc_dest_file = join(pkg_config_path, lib_to_pc_file[lib_name])
+ with open(pc_dest_file, 'w') as pc_file:
+ pc_file.write(text_buffer)
+
+ def prebuild_arch(self, arch):
+ shutil.copyfile(
+ join(self.get_recipe_dir(), "setup.cfg.template"),
+ join(self.get_build_dir(arch), "mplsetup.cfg"),
+ )
+ self.generate_libraries_pc_files(arch)
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+
+ # we make use of the same directory than `XDG_CACHE_HOME`, for our
+ # custom library pc files, so we have all the install files that we
+ # generate at the same place
+ env['XDG_CACHE_HOME'] = join(self.get_build_dir(arch), 'p4a_files')
+ env['PKG_CONFIG_PATH'] = env['XDG_CACHE_HOME']
+
+ # creating proper *.pc files for our libraries does not seem enough to
+ # success with our build (without depending on system development
+ # libraries), but if we tell the compiler where to find our libraries
+ # and includes, then the install success :)
+ 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')
+ env['CFLAGS'] += f' -I{free_inc_dir}'
+ env['LDFLAGS'] += f' -L{free_lib_dir}'
+
+ # `freetype` could be built with `harfbuzz` support,
+ # so we also include the necessary flags...just to be sure
+ if 'harfbuzz' in self.ctx.recipe_build_order:
+ harfbuzz = self.get_recipe('harfbuzz', self.ctx)
+ harf_build = harfbuzz.get_build_dir(arch.arch)
+ env['CFLAGS'] += f' -I{harf_build} -I{join(harf_build, "src")}'
+ env['LDFLAGS'] += f' -L{join(harf_build, "src", ".libs")}'
+ return env
+
+
+recipe = MatplotlibRecipe()
diff --git a/p4a/pythonforandroid/recipes/matplotlib/libfreetype.pc.template b/p4a/pythonforandroid/recipes/matplotlib/libfreetype.pc.template
new file mode 100644
index 0000000..df5ef28
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/matplotlib/libfreetype.pc.template
@@ -0,0 +1,10 @@
+prefix=path_to_built
+exec_prefix=${prefix}
+includedir=${prefix}/include
+libdir=${exec_prefix}/objs/.libs
+
+Name: freetype2
+Description: The freetype2 library
+Version: library_version
+Cflags: -I${includedir}
+Libs: -L${libdir} -lfreetype
diff --git a/p4a/pythonforandroid/recipes/matplotlib/setup.cfg.template b/p4a/pythonforandroid/recipes/matplotlib/setup.cfg.template
new file mode 100644
index 0000000..96ef80d
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/matplotlib/setup.cfg.template
@@ -0,0 +1,38 @@
+# Rename this file to mplsetup.cfg to modify Matplotlib's build options.
+
+[libs]
+# By default, Matplotlib builds with LTO, which may be slow if you re-compile
+# often, and don't need the space saving/speedup.
+enable_lto = False
+# By default, Matplotlib downloads and builds its own copies of FreeType and of
+# Qhull. You may set the following to True to instead link against a system
+# FreeType/Qhull. As an exception, Matplotlib defaults to the system version
+# of FreeType on AIX.
+system_freetype = True
+#system_qhull = False
+
+[packages]
+# There are a number of data subpackages from Matplotlib that are
+# considered optional. All except 'tests' data (meaning the baseline
+# image files) are installed by default, but that can be changed here.
+#tests = False
+
+[gui_support]
+# Matplotlib supports multiple GUI toolkits, known as backends.
+# The MacOSX backend requires the Cocoa headers included with XCode.
+# You can select whether to build it by uncommenting the following line.
+# It is never built on Linux or Windows, regardless of the config value.
+#
+macosx = False
+
+[rc_options]
+# User-configurable options
+#
+# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo,
+# MacOSX, Pdf, Ps, QtAgg, QtCairo, SVG, TkAgg, WX, WXAgg.
+#
+# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do
+# not choose MacOSX if you have disabled the relevant extension modules. The
+# default is determined by fallback.
+#
+#backend = Agg
\ No newline at end of file
diff --git a/p4a/pythonforandroid/recipes/mysqldb/__init__.py b/p4a/pythonforandroid/recipes/mysqldb/__init__.py
index f084585..768cb72 100644
--- a/p4a/pythonforandroid/recipes/mysqldb/__init__.py
+++ b/p4a/pythonforandroid/recipes/mysqldb/__init__.py
@@ -23,15 +23,15 @@ class MysqldbRecipe(CompiledComponentsPythonRecipe):
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)
+ super().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)
+ env = super().get_recipe_env(arch)
- hostpython = self.get_recipe('hostpython2', self.ctx)
+ hostpython = self.get_recipe('hostpython3', self.ctx)
# TODO: fix hardcoded path
env['PYTHONPATH'] = (join(hostpython.get_build_dir(arch.arch),
'build', 'lib.linux-x86_64-2.7') +
diff --git a/p4a/pythonforandroid/recipes/ndghttpsclient b/p4a/pythonforandroid/recipes/ndghttpsclient
index 35e996f..bfe581a 100644
--- a/p4a/pythonforandroid/recipes/ndghttpsclient
+++ b/p4a/pythonforandroid/recipes/ndghttpsclient
@@ -1,9 +1,9 @@
from pythonforandroid.recipe import PythonRecipe
class NdgHttpsClientRecipe(PythonRecipe):
- version = '0.4.0'
+ version = '0.5.1'
url = 'https://pypi.python.org/packages/source/n/ndg-httpsclient/ndg_httpsclient-{version}.tar.gz'
- depends = ['python2', 'pyopenssl', 'cryptography']
+ depends = ['python3', 'pyopenssl', 'cryptography']
call_hostpython_via_targetpython = False
recipe = NdgHttpsClientRecipe()
diff --git a/p4a/pythonforandroid/recipes/numpy/__init__.py b/p4a/pythonforandroid/recipes/numpy/__init__.py
index 6b6e6b3..55a0279 100644
--- a/p4a/pythonforandroid/recipes/numpy/__init__.py
+++ b/p4a/pythonforandroid/recipes/numpy/__init__.py
@@ -1,57 +1,74 @@
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.logger import shprint, info
+from pythonforandroid.util import current_directory
from multiprocessing import cpu_count
from os.path import join
+import glob
+import sh
+import shutil
class NumpyRecipe(CompiledComponentsPythonRecipe):
- version = '1.15.1'
+ version = '1.22.3'
url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip'
site_packages_name = 'numpy'
- depends = [('python2', 'python3', 'python3crystax')]
+ depends = ['setuptools', 'cython']
+ install_in_hostpython = True
+ call_hostpython_via_targetpython = False
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')
+ join("patches", "remove-default-paths.patch"),
+ join("patches", "add_libm_explicitly_to_build.patch"),
+ join("patches", "ranlib.patch"),
]
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+
+ # _PYTHON_HOST_PLATFORM declares that we're cross-compiling
+ # and avoids issues when building on macOS for Android targets.
+ env["_PYTHON_HOST_PLATFORM"] = arch.command_prefix
+
+ # NPY_DISABLE_SVML=1 allows numpy to build for non-AVX512 CPUs
+ # See: https://github.com/numpy/numpy/issues/21196
+ env["NPY_DISABLE_SVML"] = "1"
+
+ return env
+
+ 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)):
+ hostpython = sh.Command(self.hostpython_location)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v',
+ _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 _rebuild_compiled_components(self, arch, env):
+ info('Rebuilding compiled components in {}'.format(self.name))
+
+ hostpython = sh.Command(self.real_hostpython_location)
+ shprint(hostpython, 'setup.py', 'clean', '--all', '--force', _env=env)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
+ *self.setup_extra_args)
+
def build_compiled_components(self, arch):
self.setup_extra_args = ['-j', str(cpu_count())]
- super(NumpyRecipe, self).build_compiled_components(arch)
+ self._build_compiled_components(arch)
self.setup_extra_args = []
def rebuild_compiled_components(self, arch, env):
self.setup_extra_args = ['-j', str(cpu_count())]
- super(NumpyRecipe, self).rebuild_compiled_components(arch, env)
+ 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'
+ def get_hostrecipe_env(self, arch):
+ env = super().get_hostrecipe_env(arch)
+ env['RANLIB'] = shutil.which('ranlib')
return env
diff --git a/p4a/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch b/p4a/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch
new file mode 100644
index 0000000..f9ba9e9
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch
@@ -0,0 +1,20 @@
+diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py
+index 66c07c9..d34bd93 100644
+--- a/numpy/linalg/setup.py
++++ b/numpy/linalg/setup.py
+@@ -46,6 +46,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'],
+ )
+
+ # umath_linalg module
+@@ -54,7 +54,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/p4a/pythonforandroid/recipes/numpy/patches/ar.patch b/p4a/pythonforandroid/recipes/numpy/patches/ar.patch
deleted file mode 100644
index ddb096c..0000000
--- a/p4a/pythonforandroid/recipes/numpy/patches/ar.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-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
deleted file mode 100644
index a5e0084..0000000
--- a/p4a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-diff --git a/numpy/testing/__init__.py b/numpy/testing/__init__.py
-index a7c8593..007ce26 100644
---- a/numpy/testing/__init__.py
-+++ b/numpy/testing/__init__.py
-@@ -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.
--
--"""
--from __future__ import division, absolute_import, print_function
--
--from unittest import TestCase
--
--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
deleted file mode 100644
index 194ce51..0000000
--- a/p4a/pythonforandroid/recipes/numpy/patches/lib.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-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']
- )
-
- 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'],
- )
-
- # 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))
-
-- libs = []
-+ libs = ['m']
- # Configure mtrand
- config.add_extension('mtrand',
- sources=[join('mtrand', x) for x in
diff --git a/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch b/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch
deleted file mode 100644
index 8ff3775..0000000
--- a/p4a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
-index bea120cf9..a448a83fc 100644
---- a/numpy/distutils/system_info.py
-+++ b/numpy/distutils/system_info.py
-@@ -719,6 +719,7 @@ class system_info(object):
- return self.get_paths(self.section, key)
-
- def get_libs(self, key, default):
-+ return []
- try:
- libs = self.cp.get(self.section, key)
- except NoOptionError:
diff --git a/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch b/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch
deleted file mode 100644
index 59d225d..0000000
--- a/p4a/pythonforandroid/recipes/numpy/patches/python-fixes.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-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/numpy/patches/ranlib.patch b/p4a/pythonforandroid/recipes/numpy/patches/ranlib.patch
new file mode 100644
index 0000000..c0b5dad
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/numpy/patches/ranlib.patch
@@ -0,0 +1,11 @@
+diff -Naur numpy.orig/numpy/distutils/unixccompiler.py numpy/numpy/distutils/unixccompiler.py
+--- numpy.orig/numpy/distutils/unixccompiler.py 2022-05-28 10:22:10.000000000 +0200
++++ numpy/numpy/distutils/unixccompiler.py 2022-05-28 10:22:24.000000000 +0200
+@@ -124,6 +124,7 @@
+ # platform intelligence here to skip ranlib if it's not
+ # needed -- or maybe Python's configure script took care of
+ # it for us, hence the check for leading colon.
++ self.ranlib = [os.environ.get('RANLIB')]
+ if self.ranlib:
+ display = '%s:@ %s' % (os.path.basename(self.ranlib[0]),
+ output_filename)
diff --git a/p4a/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch b/p4a/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch
new file mode 100644
index 0000000..3581f0f
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch
@@ -0,0 +1,28 @@
+diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
+index fc7018a..7b514bc 100644
+--- a/numpy/distutils/system_info.py
++++ b/numpy/distutils/system_info.py
+@@ -340,10 +340,10 @@ if os.path.join(sys.prefix, 'lib') not in default_lib_dirs:
+ default_include_dirs.append(os.path.join(sys.prefix, 'include'))
+ default_src_dirs.append(os.path.join(sys.prefix, 'src'))
+
+-default_lib_dirs = [_m for _m in default_lib_dirs if os.path.isdir(_m)]
+-default_runtime_dirs = [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
+-default_include_dirs = [_m for _m in default_include_dirs if os.path.isdir(_m)]
+-default_src_dirs = [_m for _m in default_src_dirs if os.path.isdir(_m)]
++default_lib_dirs = [] #[_m for _m in default_lib_dirs if os.path.isdir(_m)]
++default_runtime_dirs =[] # [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
++default_include_dirs =[] # [_m for _m in default_include_dirs if os.path.isdir(_m)]
++default_src_dirs =[] # [_m for _m in default_src_dirs if os.path.isdir(_m)]
+
+ so_ext = get_shared_lib_extension()
+
+@@ -814,7 +814,7 @@ class system_info(object):
+ path = self.get_paths(self.section, key)
+ if path == ['']:
+ path = []
+- return path
++ return []
+
+ def get_include_dirs(self, key='include_dirs'):
+ return self.get_paths(self.section, key)
diff --git a/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py b/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py
index c87034c..8efa815 100644
--- a/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py
+++ b/p4a/pythonforandroid/recipes/omemo-backend-signal/__init__.py
@@ -3,7 +3,7 @@ from pythonforandroid.recipe import PythonRecipe
class OmemoBackendSignalRecipe(PythonRecipe):
name = 'omemo-backend-signal'
- version = '0.2.2'
+ version = '0.2.5'
url = 'https://pypi.python.org/packages/source/o/omemo-backend-signal/omemo-backend-signal-{version}.tar.gz'
site_packages_name = 'omemo-backend-signal'
depends = [
@@ -15,7 +15,6 @@ class OmemoBackendSignalRecipe(PythonRecipe):
'cryptography',
'omemo',
]
- patches = ['wireformat.patch']
call_hostpython_via_targetpython = False
diff --git a/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch b/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch
deleted file mode 100644
index 7881d05..0000000
--- a/p4a/pythonforandroid/recipes/omemo-backend-signal/wireformat.patch
+++ /dev/null
@@ -1,101 +0,0 @@
-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
index a940105..7ea3d68 100644
--- a/p4a/pythonforandroid/recipes/omemo/__init__.py
+++ b/p4a/pythonforandroid/recipes/omemo/__init__.py
@@ -3,7 +3,7 @@ from pythonforandroid.recipe import PythonRecipe
class OmemoRecipe(PythonRecipe):
name = 'omemo'
- version = '0.10.3'
+ version = '0.11.0'
url = 'https://pypi.python.org/packages/source/O/OMEMO/OMEMO-{version}.tar.gz'
site_packages_name = 'omemo'
depends = [
diff --git a/p4a/pythonforandroid/recipes/openal/__init__.py b/p4a/pythonforandroid/recipes/openal/__init__.py
index ad93065..f5b7d01 100644
--- a/p4a/pythonforandroid/recipes/openal/__init__.py
+++ b/p4a/pythonforandroid/recipes/openal/__init__.py
@@ -1,32 +1,24 @@
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'
+ version = '1.21.1'
+ url = 'https://github.com/kcat/openal-soft/archive/refs/tags/{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)
+ "-DANDROID_STL=" + self.stl_lib_name,
+ "-DCMAKE_TOOLCHAIN_FILE={}".format(
+ join(self.ctx.ndk_dir, "build", "cmake", "android.toolchain.cmake")
+ ),
]
- if self.ctx.ndk == 'crystax':
- # avoids a segfault in libcrystax when calling lrintf
- cmake_args += ['-DHAVE_LRINTF=0']
shprint(
sh.cmake, '.',
*cmake_args,
diff --git a/p4a/pythonforandroid/recipes/opencv/__init__.py b/p4a/pythonforandroid/recipes/opencv/__init__.py
index 6932bc2..c760cbd 100644
--- a/p4a/pythonforandroid/recipes/opencv/__init__.py
+++ b/p4a/pythonforandroid/recipes/opencv/__init__.py
@@ -1,10 +1,8 @@
from os.path import join
import sh
from pythonforandroid.recipe import NDKRecipe
-from pythonforandroid.toolchain import (
- current_directory,
- shprint,
-)
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
from multiprocessing import cpu_count
@@ -15,7 +13,7 @@ class OpenCVRecipe(NDKRecipe):
build of most of the libraries of the opencv's package, so we can
process images, videos, objects, photos...
'''
- version = '4.0.1'
+ version = '4.5.1'
url = 'https://github.com/opencv/opencv/archive/{version}.zip'
depends = ['numpy']
patches = ['patches/p4a_build.patch']
@@ -33,14 +31,14 @@ class OpenCVRecipe(NDKRecipe):
'libopencv_video.so',
'libopencv_dnn.so',
'libopencv_imgcodecs.so',
- 'libopencv_photo.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 = super().get_recipe_env(arch)
env['ANDROID_NDK'] = self.ctx.ndk_dir
env['ANDROID_SDK'] = self.ctx.sdk_dir
return env
@@ -48,16 +46,24 @@ class OpenCVRecipe(NDKRecipe):
def build_arch(self, arch):
build_dir = join(self.get_build_dir(arch.arch), 'build')
shprint(sh.mkdir, '-p', build_dir)
+
+ opencv_extras = []
+ if 'opencv_extras' in self.ctx.recipe_build_order:
+ opencv_extras_dir = self.get_recipe(
+ 'opencv_extras', self.ctx).get_build_dir(arch.arch)
+ opencv_extras = [
+ f'-DOPENCV_EXTRA_MODULES_PATH={opencv_extras_dir}/modules',
+ '-DBUILD_opencv_legacy=OFF',
+ ]
+
with current_directory(build_dir):
env = self.get_recipe_env(arch)
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_site_packages = self.ctx.get_site_packages_dir(arch)
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_link_version = self.ctx.python_recipe.link_version
python_library = join(python_link_root,
'libpython{}.so'.format(python_link_version))
python_include_numpy = join(python_site_packages,
@@ -69,6 +75,8 @@ class OpenCVRecipe(NDKRecipe):
'-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']),
+ '-DANDROID_SDK_TOOLS_VERSION=6514223',
+ '-DANDROID_PROJECTS_SUPPORT_GRADLE=ON',
'-DCMAKE_TOOLCHAIN_FILE={}'.format(
join(self.ctx.ndk_dir, 'build', 'cmake',
@@ -122,6 +130,8 @@ class OpenCVRecipe(NDKRecipe):
'-DPYTHON{major}_PACKAGES_PATH={site_packages}'.format(
major=python_major, site_packages=python_site_packages),
+ *opencv_extras,
+
self.get_build_dir(arch.arch),
_env=env)
shprint(sh.make, '-j' + str(cpu_count()), 'opencv_python' + python_major)
diff --git a/p4a/pythonforandroid/recipes/opencv_extras/__init__.py b/p4a/pythonforandroid/recipes/opencv_extras/__init__.py
new file mode 100644
index 0000000..693c365
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/opencv_extras/__init__.py
@@ -0,0 +1,23 @@
+from pythonforandroid.recipe import Recipe
+
+
+class OpenCVExtrasRecipe(Recipe):
+ """
+ OpenCV extras recipe allows us to build extra modules from the
+ `opencv_contrib` repository. It depends on opencv recipe and all the build
+ of the modules will be performed inside opencv recipe build directory.
+
+ .. note:: the version of this recipe should be the same than opencv recipe.
+
+ .. warning:: Be aware that these modules are experimental, some of them
+ maybe included in opencv future releases and removed from extras.
+
+ .. seealso:: https://github.com/opencv/opencv_contrib
+
+ """
+ version = '4.5.1'
+ url = 'https://github.com/opencv/opencv_contrib/archive/{version}.zip'
+ depends = ['opencv']
+
+
+recipe = OpenCVExtrasRecipe()
diff --git a/p4a/pythonforandroid/recipes/openssl/__init__.py b/p4a/pythonforandroid/recipes/openssl/__init__.py
index 3a9505f..520fe6d 100644
--- a/p4a/pythonforandroid/recipes/openssl/__init__.py
+++ b/p4a/pythonforandroid/recipes/openssl/__init__.py
@@ -1,6 +1,8 @@
from os.path import join
-from pythonforandroid.toolchain import Recipe, shprint, current_directory
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
import sh
@@ -20,11 +22,6 @@ class OpenSSLRecipe(Recipe):
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
@@ -41,40 +38,24 @@ class OpenSSLRecipe(Recipe):
- Add ability to build a legacy version of the openssl libs when using
python2legacy or python3crystax.
+ .. versionchanged:: 2019.06.06.1.dev0
+
+ - Removed legacy version of openssl libraries
+
'''
- standard_version = '1.1'
+ 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'
+ url_version = '1.1.1m'
'''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
+ built_libraries = {
+ 'libcrypto{version}.so'.format(version=version): '.',
+ 'libssl{version}.so'.format(version=version): '.',
+ }
@property
def versioned_url(self):
@@ -83,7 +64,9 @@ class OpenSSLRecipe(Recipe):
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)
+ return join(
+ self.get_build_container_dir(arch), self.name + self.version
+ )
def include_flags(self, arch):
'''Returns a string with the include folders'''
@@ -109,27 +92,20 @@ class OpenSSLRecipe(Recipe):
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 get_recipe_env(self, arch=None):
- env = super(OpenSSLRecipe, self).get_recipe_env(arch, clang=not self.use_legacy)
+ env = super().get_recipe_env(arch)
env['OPENSSL_VERSION'] = self.version
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
+ env['CC'] = 'clang'
+ env['ANDROID_NDK_HOME'] = self.ctx.ndk_dir
return env
def select_build_arch(self, arch):
aname = arch.arch
if 'arm64' in aname:
- return 'android-arm64' if not self.use_legacy else 'linux-aarch64'
+ return 'android-arm64'
if 'v7a' in aname:
- return 'android-arm' if not self.use_legacy else 'android-armv7'
+ return 'android-arm'
if 'arm' in aname:
return 'android'
if 'x86_64' in aname:
@@ -145,29 +121,17 @@ class OpenSSLRecipe(Recipe):
# so instead we manually run perl passing in Configure
perl = sh.Command('perl')
buildarch = self.select_build_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))
+ config_args = [
+ 'shared',
+ 'no-dso',
+ 'no-asm',
+ buildarch,
+ '-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)
+ self.apply_patch('disable-sover.patch', arch.arch)
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
deleted file mode 100644
index 6099fad..0000000
--- a/p4a/pythonforandroid/recipes/openssl/disable-sover-legacy.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- 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/rename-shared-lib.patch b/p4a/pythonforandroid/recipes/openssl/rename-shared-lib.patch
deleted file mode 100644
index 30c0f79..0000000
--- a/p4a/pythonforandroid/recipes/openssl/rename-shared-lib.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- openssl/Makefile.shared 2016-05-03 15:44:42.000000000 +0200
-+++ patch/Makefile.shared 2016-07-14 00:08:37.268792948 +0200
-@@ -147,11 +147,11 @@
- DETECT_GNU_LD=($(CC) -Wl,-V /dev/null 2>&1 | grep '^GNU ld' )>/dev/null
-
- DO_GNU_SO=$(CALC_VERSIONS); \
-- SHLIB=lib$(LIBNAME).so; \
-+ SHLIB=lib$(LIBNAME)$(OPENSSL_VERSION).so; \
- SHLIB_SUFFIX=; \
- ALLSYMSFLAGS='-Wl,--whole-archive'; \
- NOALLSYMSFLAGS='-Wl,--no-whole-archive'; \
-- SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB$$SHLIB_SOVER$$SHLIB_SUFFIX"
-+ SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB"
-
- DO_GNU_APP=LDFLAGS="$(CFLAGS) -Wl,-rpath,$(LIBRPATH)"
-
diff --git a/p4a/pythonforandroid/recipes/pandas/__init__.py b/p4a/pythonforandroid/recipes/pandas/__init__.py
new file mode 100644
index 0000000..a217ab6
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/pandas/__init__.py
@@ -0,0 +1,35 @@
+from os.path import join
+
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class PandasRecipe(CppCompiledComponentsPythonRecipe):
+ version = '1.0.3'
+ url = 'https://github.com/pandas-dev/pandas/releases/download/v{version}/pandas-{version}.tar.gz' # noqa
+
+ depends = ['cython', 'numpy', 'pytz', 'libbz2', 'liblzma']
+
+ python_depends = ['python-dateutil']
+ patches = ['fix_numpy_includes.patch']
+
+ call_hostpython_via_targetpython = False
+ need_stl_shared = True
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ # we need the includes from our installed numpy at site packages
+ # because we need some includes generated at numpy's compile time
+ env['NUMPY_INCLUDES'] = join(
+ self.ctx.get_python_install_dir(arch.arch), "numpy/core/include",
+ )
+
+ # this flag below is to fix a runtime error:
+ # ImportError: dlopen failed: cannot locate symbol
+ # "_ZTVSt12length_error" referenced by
+ # "/data/data/org.test.matplotlib_testapp/files/app/_python_bundle
+ # /site-packages/pandas/_libs/window/aggregations.so"...
+ env['LDFLAGS'] += f' -landroid -l{self.stl_lib_name}'
+ return env
+
+
+recipe = PandasRecipe()
diff --git a/p4a/pythonforandroid/recipes/pandas/fix_numpy_includes.patch b/p4a/pythonforandroid/recipes/pandas/fix_numpy_includes.patch
new file mode 100644
index 0000000..ef1643b
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/pandas/fix_numpy_includes.patch
@@ -0,0 +1,31 @@
+--- pandas-1.0.1/setup.py.orig 2020-02-05 17:15:24.000000000 +0100
++++ pandas-1.0.1/setup.py 2020-03-15 13:47:57.612237225 +0100
+@@ -37,11 +37,12 @@ min_cython_ver = "0.29.13" # note: sync
+
+ setuptools_kwargs = {
+ "install_requires": [
+- "python-dateutil >= 2.6.1",
+- "pytz >= 2017.2",
+- f"numpy >= {min_numpy_ver}",
++ # dependencies managed via p4a's recipe
++ # "python-dateutil >= 2.6.1",
++ # "pytz >= 2017.2",
++ # f"numpy >= {min_numpy_ver}",
+ ],
+- "setup_requires": [f"numpy >= {min_numpy_ver}"],
++ "setup_requires": [],
+ "zip_safe": False,
+ }
+
+@@ -514,7 +515,10 @@ def maybe_cythonize(extensions, *args, *
+ )
+ raise RuntimeError("Cannot cythonize without Cython installed.")
+
+- numpy_incl = pkg_resources.resource_filename("numpy", "core/include")
++ if 'NUMPY_INCLUDES' in os.environ:
++ numpy_incl = os.environ['NUMPY_INCLUDES']
++ else:
++ numpy_incl = pkg_resources.resource_filename("numpy", "core/include")
+ # TODO: Is this really necessary here?
+ for ext in extensions:
+ if hasattr(ext, "include_dirs") and numpy_incl not in ext.include_dirs:
diff --git a/p4a/pythonforandroid/recipes/pbkdf2/__init__.py b/p4a/pythonforandroid/recipes/pbkdf2/__init__.py
deleted file mode 100644
index d1d5d7b..0000000
--- a/p4a/pythonforandroid/recipes/pbkdf2/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class Pbkdf2Recipe(PythonRecipe):
-
- # TODO: version
- url = 'https://github.com/dlitz/python-pbkdf2/archive/master.zip'
-
- depends = ['setuptools']
-
-
-recipe = Pbkdf2Recipe()
diff --git a/p4a/pythonforandroid/recipes/pil/__init__.py b/p4a/pythonforandroid/recipes/pil/__init__.py
index f3ad2f4..46bace1 100644
--- a/p4a/pythonforandroid/recipes/pil/__init__.py
+++ b/p4a/pythonforandroid/recipes/pil/__init__.py
@@ -1,79 +1,23 @@
-from os.path import join, exists
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from pythonforandroid.toolchain import shprint
-import sh
+from pythonforandroid.recipes.Pillow import PillowRecipe
+from pythonforandroid.logger import warning
-class PILRecipe(CompiledComponentsPythonRecipe):
- 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'
+class PilRecipe(PillowRecipe):
+ """A transparent wrapper around the Pillow recipe, it should build
+ Pillow automatically as if "pillow" were specified in the
+ requirements.
+ """
- patches = ['disable-tk.patch',
- 'fix-directories.patch']
+ name = 'Pillow' # ensures the Pillow recipe directory is used where necessary
- def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(PILRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ conflicts = ['pillow']
- env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch)
- env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
-
- 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)
-
- 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
+ def build_arch(self, arch):
+ warning('PIL is no longer supported, building Pillow instead. '
+ 'This should be a drop-in replacement.')
+ warning('It is recommended to change "pil" to "pillow" in your requirements, '
+ 'to ensure future compatibility')
+ super().build_arch(arch)
-recipe = PILRecipe()
+recipe = PilRecipe()
diff --git a/p4a/pythonforandroid/recipes/pil/disable-tk.patch b/p4a/pythonforandroid/recipes/pil/disable-tk.patch
deleted file mode 100644
index c6934c9..0000000
--- a/p4a/pythonforandroid/recipes/pil/disable-tk.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- Imaging-1.1.7/setup.py.orig 2012-08-31 12:52:25.000000000 +0200
-+++ Imaging-1.1.7/setup.py 2012-08-31 12:53:04.000000000 +0200
-@@ -322,7 +322,7 @@
- "_imagingcms", ["_imagingcms.c"], libraries=["lcms"] + extra
- ))
-
-- if sys.platform == "darwin":
-+ if False: #sys.platform == "darwin":
- # locate Tcl/Tk frameworks
- frameworks = []
- framework_roots = [
-
diff --git a/p4a/pythonforandroid/recipes/pil/fix-directories.patch b/p4a/pythonforandroid/recipes/pil/fix-directories.patch
deleted file mode 100644
index b9daee3..0000000
--- a/p4a/pythonforandroid/recipes/pil/fix-directories.patch
+++ /dev/null
@@ -1,85 +0,0 @@
---- 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
--JPEG_ROOT = None
--ZLIB_ROOT = None
-+JPEG_ROOT = tuple(os.environ['JPEG_ROOT'].split('|')) if 'JPEG_ROOT' in os.environ else None
-+ZLIB_ROOT = tuple(os.environ['ZLIB_ROOT'].split('|')) if 'ZLIB_ROOT' in os.environ else 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
-
- # FIXME: add mechanism to explicitly *disable* the use of a library
-@@ -127,33 +127,10 @@ class pil_build_ext(build_ext):
-
- add_directory(include_dirs, "libImaging")
-
-- #
-- # add platform directories
--
-- if sys.platform == "cygwin":
-- # pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
-- add_directory(library_dirs, os.path.join(
-- "/usr/lib", "python%s" % sys.version[:3], "config"
-- ))
--
-- elif sys.platform == "darwin":
-- # attempt to make sure we pick freetype2 over other versions
-- add_directory(include_dirs, "/sw/include/freetype2")
-- add_directory(include_dirs, "/sw/lib/freetype2/include")
-- # fink installation directories
-- add_directory(library_dirs, "/sw/lib")
-- add_directory(include_dirs, "/sw/include")
-- # darwin ports installation directories
-- add_directory(library_dirs, "/opt/local/lib")
-- add_directory(include_dirs, "/opt/local/include")
--
-- add_directory(library_dirs, "/usr/local/lib")
-- # FIXME: check /opt/stuff directories here?
--
-- prefix = sysconfig.get_config_var("prefix")
-+ prefix = os.environ.get('PYTHON_LINK_ROOT')
- if prefix:
-- 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)
-
- #
-- # add standard directories
--
-- # look for tcl specific subdirectory (e.g debian)
-- if _tkinter:
-- tcl_dir = "/usr/include/tcl" + TCL_VERSION
-- if os.path.isfile(os.path.join(tcl_dir, "tk.h")):
-- add_directory(include_dirs, tcl_dir)
--
-- # standard locations
-- add_directory(library_dirs, "/usr/local/lib")
-- add_directory(include_dirs, "/usr/local/include")
--
-- add_directory(library_dirs, "/usr/lib")
-- add_directory(include_dirs, "/usr/include")
--
-- #
- # insert new dirs *before* default libs, to avoid conflicts
- # between Python PYD stub libs and real libraries
-
-@@ -299,8 +260,6 @@ class pil_build_ext(build_ext):
- defs.append(("HAVE_LIBZ", None))
- if sys.platform == "win32":
- libs.extend(["kernel32", "user32", "gdi32"])
-- if struct.unpack("h", "\0\1")[0] == 1:
-- defs.append(("WORDS_BIGENDIAN", None))
-
- exts = [(Extension(
- "_imaging", files, libraries=libs, define_macros=defs
diff --git a/p4a/pythonforandroid/recipes/png/__init__.py b/p4a/pythonforandroid/recipes/png/__init__.py
index 5b69688..6138195 100644
--- a/p4a/pythonforandroid/recipes/png/__init__.py
+++ b/p4a/pythonforandroid/recipes/png/__init__.py
@@ -1,19 +1,30 @@
-from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from multiprocessing import cpu_count
+import sh
-class PngRecipe(NDKRecipe):
+class PngRecipe(Recipe):
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'
+ version = '1.6.37'
+ url = 'https://github.com/glennrp/libpng/archive/v{version}.zip'
+ built_libraries = {'libpng16.so': '.libs'}
- # TODO: Try to move the repo to mainline
- url = 'https://github.com/julienr/libpng-android/archive/{version}.zip'
-
- generated_libraries = ['libpng.a']
+ def build_arch(self, arch):
+ build_dir = self.get_build_dir(arch.arch)
+ with current_directory(build_dir):
+ env = self.get_recipe_env(arch)
+ shprint(
+ sh.Command('./configure'),
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--disable-static',
+ '--enable-shared',
+ '--prefix={}/install'.format(self.get_build_dir(arch.arch)),
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
recipe = PngRecipe()
diff --git a/p4a/pythonforandroid/recipes/png/build_shared_library.patch b/p4a/pythonforandroid/recipes/png/build_shared_library.patch
new file mode 100644
index 0000000..01e3080
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/png/build_shared_library.patch
@@ -0,0 +1,17 @@
+diff --git a/jni/Android.mk b/jni/Android.mk
+index df2ff1a..2f70985 100644
+--- a/jni/Android.mk
++++ b/jni/Android.mk
+@@ -26,8 +26,9 @@ LOCAL_SRC_FILES :=\
+ arm/filter_neon_intrinsics.c
+
+ #LOCAL_SHARED_LIBRARIES := -lz
+-LOCAL_EXPORT_LDLIBS := -lz
++# LOCAL_EXPORT_LDLIBS := -lz
++LOCAL_LDLIBS := -lz
+ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.
+
+-#include $(BUILD_SHARED_LIBRARY)
+-include $(BUILD_STATIC_LIBRARY)
++include $(BUILD_SHARED_LIBRARY)
++# include $(BUILD_STATIC_LIBRARY)
diff --git a/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py
index 30ca030..c1149f2 100644
--- a/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py
+++ b/p4a/pythonforandroid/recipes/protobuf_cpp/__init__.py
@@ -1,6 +1,6 @@
-from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
from pythonforandroid.logger import shprint, info_notify
-from pythonforandroid.util import current_directory, shutil
+from pythonforandroid.util import current_directory
from os.path import exists, join
import sh
from multiprocessing import cpu_count
@@ -9,17 +9,23 @@ import sys
import os
-class ProtobufCppRecipe(PythonRecipe):
+class ProtobufCppRecipe(CppCompiledComponentsPythonRecipe):
+ """This is a two-in-one recipe:
+ - build labraru `libprotobuf.so`
+ - build and install python binding for protobuf_cpp
+ """
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'
+ setup_extra_args = ['--cpp_implementation']
+ built_libraries = {'libprotobuf.so': 'src/.libs'}
protoc_dir = None
def prebuild_arch(self, arch):
- super(ProtobufCppRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
patch_mark = join(self.get_build_dir(arch.arch), '.protobuf-patched')
if self.ctx.python_recipe.name == 'python3' and not exists(patch_mark):
@@ -65,42 +71,39 @@ class ProtobufCppRecipe(PythonRecipe):
def build_arch(self, arch):
env = self.get_recipe_env(arch)
- # Build libproto.a
+ # Build libproto.so
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]
+ 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('./configure'),
- '--host={}'.format(env['HOSTARCH']),
+ '--build={}'.format(build_arch),
+ '--host={}'.format(arch.command_prefix),
+ '--target={}'.format(arch.command_prefix),
+ '--disable-static',
'--enable-shared',
_env=env)
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'))
- # 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'))
+ self.install_python_package(arch)
+ def build_compiled_components(self, arch):
# Build python bindings and _message.so
+ env = self.get_recipe_env(arch)
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)
-
- # 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()
+ _env=env, *self.setup_extra_args)
def install_python_package(self, arch):
env = self.get_recipe_env(arch)
@@ -112,34 +115,27 @@ class ProtobufCppRecipe(PythonRecipe):
hpenv = env.copy()
shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
'--install-lib=.',
- '--cpp_implementation',
_env=hpenv, *self.setup_extra_args)
+ # Create __init__.py which is missing, see also:
+ # - https://github.com/protocolbuffers/protobuf/issues/1296
+ # - https://stackoverflow.com/questions/13862562/
+ # google-protocol-buffers-not-found-when-trying-to-freeze-python-app
+ open(
+ join(self.ctx.get_site_packages_dir(arch), 'google', '__init__.py'),
+ 'a',
+ ).close()
+
def get_recipe_env(self, arch):
- env = super(ProtobufCppRecipe, self).get_recipe_env(arch)
+ env = super().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)
+ env['CXXFLAGS'] += ' -std=c++11'
+ env['LDFLAGS'] += ' -lm -landroid -llog'
return env
diff --git a/p4a/pythonforandroid/recipes/psycopg2/__init__.py b/p4a/pythonforandroid/recipes/psycopg2/__init__.py
index aaf5a33..1d946e7 100644
--- a/p4a/pythonforandroid/recipes/psycopg2/__init__.py
+++ b/p4a/pythonforandroid/recipes/psycopg2/__init__.py
@@ -10,9 +10,9 @@ class Psycopg2Recipe(PythonRecipe):
`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 = ['libpq']
+ version = '2.8.5'
+ url = 'https://pypi.python.org/packages/source/p/psycopg2/psycopg2-{version}.tar.gz'
+ depends = ['libpq', 'setuptools']
site_packages_name = 'psycopg2'
call_hostpython_via_targetpython = False
@@ -26,7 +26,7 @@ class Psycopg2Recipe(PythonRecipe):
'setup.py')
def get_recipe_env(self, arch):
- env = super(Psycopg2Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['LDFLAGS'] = "{} -L{}".format(env['LDFLAGS'], self.ctx.get_libs_dir(arch.arch))
env['EXTRA_CFLAGS'] = "--host linux-armv"
return env
@@ -43,7 +43,7 @@ class Psycopg2Recipe(PythonRecipe):
shprint(hostpython, 'setup.py', 'build_ext', '--static-libpq',
_env=env)
shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
'--install-lib=.', _env=env)
diff --git a/p4a/pythonforandroid/recipes/py3dns/__init__.py b/p4a/pythonforandroid/recipes/py3dns/__init__.py
new file mode 100644
index 0000000..bccb39f
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/py3dns/__init__.py
@@ -0,0 +1,13 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class Py3DNSRecipe(PythonRecipe):
+ site_packages_name = 'DNS'
+ version = '3.2.1'
+ url = 'https://launchpad.net/py3dns/trunk/{version}/+download/py3dns-{version}.tar.gz'
+ depends = ['setuptools']
+ patches = ['patches/android.patch']
+ call_hostpython_via_targetpython = False
+
+
+recipe = Py3DNSRecipe()
diff --git a/p4a/pythonforandroid/recipes/py3dns/patches/android.patch b/p4a/pythonforandroid/recipes/py3dns/patches/android.patch
new file mode 100644
index 0000000..f9ab78f
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/py3dns/patches/android.patch
@@ -0,0 +1,27 @@
+diff --git a/DNS/Base.py b/DNS/Base.py
+index 34a6da7..a558889 100644
+--- a/DNS/Base.py
++++ b/DNS/Base.py
+@@ -15,6 +15,7 @@ import socket, string, types, time, select
+ import errno
+ from . import Type,Class,Opcode
+ import asyncore
++import os
+ #
+ # This random generator is used for transaction ids and port selection. This
+ # is important to prevent spurious results from lost packets, and malicious
+@@ -50,8 +51,12 @@ defaults= { 'protocol':'udp', 'port':53, 'opcode':Opcode.QUERY,
+
+ def ParseResolvConf(resolv_path="/etc/resolv.conf"):
+ "parses the /etc/resolv.conf file and sets defaults for name servers"
+- with open(resolv_path, 'r') as stream:
+- return ParseResolvConfFromIterable(stream)
++ if os.path.exists(resolv_path):
++ with open(resolv_path, 'r') as stream:
++ return ParseResolvConfFromIterable(stream)
++ else:
++ defaults['server'].append('127.0.0.1')
++ return
+
+ def ParseResolvConfFromIterable(lines):
+ "parses a resolv.conf formatted stream and sets defaults for name servers"
diff --git a/p4a/pythonforandroid/recipes/pyasn1/__init__.py b/p4a/pythonforandroid/recipes/pyasn1/__init__.py
deleted file mode 100644
index 9befc59..0000000
--- a/p4a/pythonforandroid/recipes/pyasn1/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-from pythonforandroid.recipe import PythonRecipe
-
-
-class PyASN1Recipe(PythonRecipe):
- version = '0.4.5'
- url = 'https://pypi.python.org/packages/source/p/pyasn1/pyasn1-{version}.tar.gz'
- depends = []
-
-
-recipe = PyASN1Recipe()
diff --git a/p4a/pythonforandroid/recipes/pybind11/__init__.py b/p4a/pythonforandroid/recipes/pybind11/__init__.py
new file mode 100644
index 0000000..affff81
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/pybind11/__init__.py
@@ -0,0 +1,17 @@
+from pythonforandroid.recipe import PythonRecipe
+from os.path import join
+
+
+class Pybind11Recipe(PythonRecipe):
+
+ version = '2.9.0'
+ url = 'https://github.com/pybind/pybind11/archive/refs/tags/v{version}.zip'
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_include_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'include')
+
+
+recipe = Pybind11Recipe()
diff --git a/p4a/pythonforandroid/recipes/pycrypto/__init__.py b/p4a/pythonforandroid/recipes/pycrypto/__init__.py
index e8bfab2..f142d37 100644
--- a/p4a/pythonforandroid/recipes/pycrypto/__init__.py
+++ b/p4a/pythonforandroid/recipes/pycrypto/__init__.py
@@ -10,13 +10,13 @@ import sh
class PyCryptoRecipe(CompiledComponentsPythonRecipe):
version = '2.7a1'
url = 'https://github.com/dlitz/pycrypto/archive/v{version}.zip'
- depends = ['openssl', ('python2', 'python3')]
+ depends = ['openssl', 'python3']
site_packages_name = 'Crypto'
call_hostpython_via_targetpython = False
patches = ['add_length.patch']
- def get_recipe_env(self, arch=None, clang=True):
- env = super(PyCryptoRecipe, self).get_recipe_env(arch)
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
env['CC'] = env['CC'] + openssl_recipe.include_flags(arch)
@@ -36,9 +36,9 @@ class PyCryptoRecipe(CompiledComponentsPythonRecipe):
with current_directory(self.get_build_dir(arch.arch)):
configure = sh.Command('./configure')
shprint(configure, '--host=arm-eabi',
- '--prefix={}'.format(self.ctx.get_python_install_dir()),
+ '--prefix={}'.format(self.ctx.get_python_install_dir(arch.arch)),
'--enable-shared', _env=env)
- super(PyCryptoRecipe, self).build_compiled_components(arch)
+ super().build_compiled_components(arch)
recipe = PyCryptoRecipe()
diff --git a/p4a/pythonforandroid/recipes/pydantic/__init__.py b/p4a/pythonforandroid/recipes/pydantic/__init__.py
new file mode 100644
index 0000000..eb4c504
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/pydantic/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class PydanticRecipe(PythonRecipe):
+ version = '1.8.2'
+ url = 'https://github.com/samuelcolvin/pydantic/archive/refs/tags/v{version}.zip'
+ depends = ['setuptools']
+ python_depends = ['Cython', 'devtools', 'email-validator', 'dataclasses', 'typing-extensions', 'python-dotenv']
+ call_hostpython_via_targetpython = False
+
+
+recipe = PydanticRecipe()
diff --git a/p4a/pythonforandroid/recipes/pyethereum/__init__.py b/p4a/pythonforandroid/recipes/pyethereum/__init__.py
deleted file mode 100644
index d18ad8e..0000000
--- a/p4a/pythonforandroid/recipes/pyethereum/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class PyethereumRecipe(PythonRecipe):
- version = 'v1.6.1'
- url = 'https://github.com/ethereum/pyethereum/archive/{version}.tar.gz'
-
- depends = [
- 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt'
- ]
-
- call_hostpython_via_targetpython = False
-
-
-recipe = PyethereumRecipe()
diff --git a/p4a/pythonforandroid/recipes/pygame/Setup b/p4a/pythonforandroid/recipes/pygame/Setup
deleted file mode 100644
index 601802e..0000000
--- a/p4a/pythonforandroid/recipes/pygame/Setup
+++ /dev/null
@@ -1,72 +0,0 @@
-#This Setup file is used by the setup.py script to configure the
-#python extensions. You will likely use the "config.py" which will
-#build a correct Setup file for you based on your system settings.
-#If not, the format is simple enough to edit by hand. First change
-#the needed commandline flags for each dependency, then comment out
-#any unavailable optional modules in the first optional section.
-
-
-#--StartConfig
-SDL = -D_REENTRANT -lsdl -lm
-FONT = -lsdl_ttf
-IMAGE = -lsdl_image
-MIXER = -lsdl_mixer
-SMPEG = -lsmpeg
-PNG = -lpng -lz
-JPEG = -ljpeg
-SCRAP = -lX11
-PORTMIDI = -lportmidi
-PORTTIME = -lporttime
-#--EndConfig
-
-#DEBUG = -C-W -C-Wall
-DEBUG =
-
-#the following modules are optional. you will want to compile
-#everything you can, but you can ignore ones you don't have
-#dependencies for, just comment them out
-
-imageext src/imageext.c $(SDL) $(IMAGE) $(PNG) $(JPEG) $(DEBUG)
-font src/font.c $(SDL) $(FONT) $(DEBUG)
-# mixer src/mixer.c $(SDL) $(MIXER) $(DEBUG)
-# mixer_music src/music.c $(SDL) $(MIXER) $(DEBUG)
-# _numericsurfarray src/_numericsurfarray.c $(SDL) $(DEBUG)
-# _numericsndarray src/_numericsndarray.c $(SDL) $(MIXER) $(DEBUG)
-# movie src/movie.c $(SDL) $(SMPEG) $(DEBUG)
-# scrap src/scrap.c $(SDL) $(SCRAP) $(DEBUG)
-# _camera src/_camera.c src/camera_v4l2.c src/camera_v4l.c $(SDL) $(DEBUG)
-# pypm src/pypm.c $(SDL) $(PORTMIDI) $(PORTTIME) $(DEBUG)
-
-GFX = src/SDL_gfx/SDL_gfxPrimitives.c
-#GFX = src/SDL_gfx/SDL_gfxBlitFunc.c src/SDL_gfx/SDL_gfxPrimitives.c
-gfxdraw src/gfxdraw.c $(SDL) $(GFX) $(DEBUG)
-
-
-
-#these modules are required for pygame to run. they only require
-#SDL as a dependency. these should not be altered
-
-base src/base.c $(SDL) $(DEBUG)
-cdrom src/cdrom.c $(SDL) $(DEBUG)
-color src/color.c $(SDL) $(DEBUG)
-constants src/constants.c $(SDL) $(DEBUG)
-display src/display.c $(SDL) $(DEBUG)
-event src/event.c $(SDL) $(DEBUG)
-fastevent src/fastevent.c src/fastevents.c $(SDL) $(DEBUG)
-key src/key.c $(SDL) $(DEBUG)
-mouse src/mouse.c $(SDL) $(DEBUG)
-rect src/rect.c $(SDL) $(DEBUG)
-rwobject src/rwobject.c $(SDL) $(DEBUG)
-surface src/surface.c src/alphablit.c src/surface_fill.c $(SDL) $(DEBUG)
-surflock src/surflock.c $(SDL) $(DEBUG)
-time src/time.c $(SDL) $(DEBUG)
-joystick src/joystick.c $(SDL) $(DEBUG)
-draw src/draw.c $(SDL) $(DEBUG)
-image src/image.c $(SDL) $(DEBUG)
-overlay src/overlay.c $(SDL) $(DEBUG)
-transform src/transform.c src/rotozoom.c src/scale2x.c src/scale_mmx.c $(SDL) $(DEBUG) -D_NO_MMX_FOR_X86_64
-mask src/mask.c src/bitmask.c $(SDL) $(DEBUG)
-bufferproxy src/bufferproxy.c $(SDL) $(DEBUG)
-pixelarray src/pixelarray.c $(SDL) $(DEBUG)
-_arraysurfarray src/_arraysurfarray.c $(SDL) $(DEBUG)
-
diff --git a/p4a/pythonforandroid/recipes/pygame/__init__.py b/p4a/pythonforandroid/recipes/pygame/__init__.py
index 981fa44..99124de 100644
--- a/p4a/pythonforandroid/recipes/pygame/__init__.py
+++ b/p4a/pythonforandroid/recipes/pygame/__init__.py
@@ -1,74 +1,67 @@
-
-from pythonforandroid.recipe import Recipe
-from pythonforandroid.util import current_directory, ensure_dir
-from pythonforandroid.logger import debug, shprint, info, warning
from os.path import join
-import sh
-import glob
+
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.toolchain import current_directory
-class PygameRecipe(Recipe):
+class Pygame2Recipe(CompiledComponentsPythonRecipe):
+ """
+ Recipe to build apps based on SDL2-based pygame.
+
+ .. warning:: Some pygame functionality is still untested, and some
+ dependencies like freetype, postmidi and libjpeg are currently
+ not part of the build. It's usable, but not complete.
+ """
+
+ version = '2.1.0'
+ url = 'https://github.com/pygame/pygame/archive/{version}.tar.gz'
+
+ site_packages_name = 'pygame'
name = 'pygame'
- version = '1.9.1'
- url = 'http://pygame.org/ftp/pygame-{version}release.tar.gz'
- depends = ['python2legacy', 'sdl']
- conflicts = ['sdl2']
-
- patches = ['patches/fix-surface-access.patch',
- 'patches/fix-array-surface.patch',
- 'patches/fix-sdl-spam-log.patch']
-
- def get_recipe_env(self, arch=None):
- env = super(PygameRecipe, self).get_recipe_env(arch)
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch))
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
- env['LIBLINK'] = 'NOTNONE'
- env['NDKPLATFORM'] = self.ctx.ndk_platform
-
- # Every recipe uses its own liblink path, object files are collected and biglinked later
- liblink_path = join(self.get_build_container_dir(arch.arch), 'objects_{}'.format(self.name))
- env['LIBLINK_PATH'] = liblink_path
- ensure_dir(liblink_path)
- return env
+ depends = ['sdl2', 'sdl2_image', 'sdl2_mixer', 'sdl2_ttf', 'setuptools', 'jpeg', 'png']
+ call_hostpython_via_targetpython = False # Due to setuptools
+ install_in_hostpython = False
def prebuild_arch(self, arch):
- if self.is_patched(arch):
- return
- shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'),
- join(self.get_build_dir(arch.arch), 'Setup'))
-
- def build_arch(self, arch):
- 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(
- jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
- env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl_ttf -I{jni_path}/sdl_image'.format(
- 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'])
-
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
-
+ super().prebuild_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
- info('hostpython is ' + self.ctx.hostpython)
- hostpython = sh.Command(self.ctx.hostpython)
- shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
- _tail=10, _critical=True)
+ setup_template = open(join("buildconfig", "Setup.Android.SDL2.in")).read()
+ env = self.get_recipe_env(arch)
+ env['ANDROID_ROOT'] = join(self.ctx.ndk.sysroot, 'usr')
- info('strip is ' + env['STRIP'])
- build_lib = glob.glob('./build/lib*')
- assert len(build_lib) == 1
- print('stripping pygame')
- shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
- env['STRIP'], '{}', ';')
+ png = self.get_recipe('png', self.ctx)
+ png_lib_dir = join(png.get_build_dir(arch.arch), '.libs')
+ png_inc_dir = png.get_build_dir(arch)
- warning('Should remove pygame tests etc. here, but skipping for now')
+ jpeg = self.get_recipe('jpeg', self.ctx)
+ jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
+
+ sdl_mixer_includes = ""
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):
+ sdl_mixer_includes += f"-I{include_dir} "
+
+ setup_file = setup_template.format(
+ sdl_includes=(
+ " -I" + join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') +
+ " -L" + join(self.ctx.bootstrap.build_dir, "libs", str(arch)) +
+ " -L" + png_lib_dir + " -L" + jpeg_lib_dir + " -L" + arch.ndk_lib_dir_versioned),
+ sdl_ttf_includes="-I"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),
+ sdl_image_includes="-I"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'),
+ sdl_mixer_includes=sdl_mixer_includes,
+ jpeg_includes="-I"+jpeg_inc_dir,
+ png_includes="-I"+png_inc_dir,
+ freetype_includes=""
+ )
+ open("Setup", "w").write(setup_file)
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['USE_SDL2'] = '1'
+ env["PYGAME_CROSS_COMPILE"] = "TRUE"
+ env["PYGAME_ANDROID"] = "TRUE"
+ return env
-recipe = PygameRecipe()
+recipe = Pygame2Recipe()
diff --git a/p4a/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch b/p4a/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch
deleted file mode 100644
index ab74d3e..0000000
--- a/p4a/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch
+++ /dev/null
@@ -1,62 +0,0 @@
---- pygame-1.9.1release/src/_arraysurfarray.c.orig 2009-05-26 23:15:24.000000000 +0200
-+++ pygame-1.9.1release/src/_arraysurfarray.c 2012-01-06 15:10:08.273825849 +0100
-@@ -193,9 +193,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint8, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint8, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -223,9 +220,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint16, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint16, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -250,9 +244,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D(Uint16, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D(Uint16, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -316,9 +307,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D_24(Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D_24(Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -335,9 +323,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint32, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint32, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -362,9 +347,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D(Uint32, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D(Uint32, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
diff --git a/p4a/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch b/p4a/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch
deleted file mode 100644
index d78b5b5..0000000
--- a/p4a/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch
+++ /dev/null
@@ -1,47 +0,0 @@
---- pygame-1.9.1release/src/joystick.c.orig
-+++ pygame-1.9.1release/src/joystick.c
-@@ -201,7 +201,7 @@ joy_get_axis (PyObject* self, PyObject* args)
- }
-
- value = SDL_JoystickGetAxis (joy, axis);
-- printf("SDL_JoystickGetAxis value:%d:\n", value);
-+/* printf("SDL_JoystickGetAxis value:%d:\n", value); */
-
-
- return PyFloat_FromDouble (value / 32768.0);
-@@ -241,7 +241,7 @@ joy_get_button (PyObject* self, PyObject* args)
- }
-
- value = SDL_JoystickGetButton (joy, _index);
-- printf("SDL_JoystickGetButton value:%d:\n", value);
-+/* printf("SDL_JoystickGetButton value:%d:\n", value); */
-
- return PyInt_FromLong (value);
- }
-@@ -277,7 +277,7 @@ joy_get_ball (PyObject* self, PyObject* args)
- return RAISE (PyExc_SDLError, "Joystick not initialized");
- }
- value = SDL_JoystickNumBalls (joy);
-- printf("SDL_JoystickNumBalls value:%d:\n", value);
-+/* printf("SDL_JoystickNumBalls value:%d:\n", value); */
-
- if (_index < 0 || _index >= value) {
- return RAISE (PyExc_SDLError, "Invalid joystick trackball");
-@@ -300,7 +300,7 @@ joy_get_numhats (PyObject* self)
- }
-
- value = SDL_JoystickNumHats (joy);
-- printf("SDL_JoystickNumHats value:%d:\n", value);
-+/* printf("SDL_JoystickNumHats value:%d:\n", value); */
-
- return PyInt_FromLong (value);
- }
-@@ -327,7 +327,7 @@ joy_get_hat (PyObject* self, PyObject* args)
-
- px = py = 0;
- value = SDL_JoystickGetHat (joy, _index);
-- printf("SDL_JoystickGetHat value:%d:\n", value);
-+/* printf("SDL_JoystickGetHat value:%d:\n", value); */
-
- if (value & SDL_HAT_UP) {
- py = 1;
diff --git a/p4a/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch b/p4a/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch
deleted file mode 100644
index 66f7c27..0000000
--- a/p4a/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- pygame-1.9.1release/src/surface.c.orig 2012-01-06 15:05:14.457829356 +0100
-+++ pygame-1.9.1release/src/surface.c 2012-01-06 15:05:26.009829217 +0100
-@@ -1722,7 +1722,7 @@
- {
- SDL_Surface *surf = PySurface_AsSurface (self);
- /* Need to use 64bit vars so this works on 64 bit pythons. */
-- Uint64 r, g, b, a;
-+ unsigned long r, g, b, a;
-
- if (!PyArg_ParseTuple (args, "(kkkk)", &r, &g, &b, &a))
- return NULL;
-@@ -1734,10 +1734,12 @@
- printf("what are: %d, %d, %d, %d\n", surf->format->Rmask, surf->format->Gmask, surf->format->Bmask, surf->format->Amask);
- */
-
-- surf->format->Rmask = (Uint32)r;
-- surf->format->Gmask = (Uint32)g;
-- surf->format->Bmask = (Uint32)b;
-- surf->format->Amask = (Uint32)a;
-+ SDL_PixelFormat *spf = surf->format;
-+
-+ spf->Rmask = (Uint32)r;
-+ spf->Gmask = (Uint32)g;
-+ spf->Bmask = (Uint32)b;
-+ spf->Amask = (Uint32)a;
-
- Py_RETURN_NONE;
- }
-@@ -1762,7 +1764,7 @@
- surf_set_shifts (PyObject *self, PyObject *args)
- {
- SDL_Surface *surf = PySurface_AsSurface (self);
-- Uint64 r, g, b, a;
-+ unsigned long r, g, b, a;
-
- if (!PyArg_ParseTuple (args, "(kkkk)", &r, &g, &b, &a))
- return NULL;
diff --git a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py b/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
deleted file mode 100644
index f18dd5b..0000000
--- a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
+++ /dev/null
@@ -1,34 +0,0 @@
-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).prebuild_arch(arch)
-
- info('Unpacking pygame bootstrap JNI dir components')
- with current_directory(self.get_build_container_dir(arch)):
- if exists('sdl'):
- info('sdl dir exists, so it looks like the JNI components' +
- 'are already unpacked. Skipping.')
- return
- for dirn in glob.glob(join(self.get_build_dir(arch),
- 'pygame_bootstrap_jni', '*')):
- 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
deleted file mode 100644
index 9992084..0000000
--- a/p4a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-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 98ec7b7..d1e3749 100644
--- a/p4a/pythonforandroid/recipes/pyicu/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyicu/__init__.py
@@ -1,58 +1,29 @@
-import os
-import sh
from os.path import join
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from pythonforandroid.toolchain import shprint, info
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
-class PyICURecipe(CompiledComponentsPythonRecipe):
+class PyICURecipe(CppCompiledComponentsPythonRecipe):
version = '1.9.2'
- url = 'https://pypi.python.org/packages/source/P/PyICU/PyICU-{version}.tar.gz'
+ url = ('https://pypi.python.org/packages/source/P/PyICU/'
+ 'PyICU-{version}.tar.gz')
depends = ["icu"]
- patches = ['locale.patch', 'icu.patch']
+ patches = ['locale.patch']
def get_recipe_env(self, arch):
- env = super(PyICURecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
icu_include = join(
- self.ctx.get_python_install_dir(), "include", "icu")
+ self.ctx.get_python_install_dir(arch.arch), "include", "icu")
- env["CC"] += " -I"+icu_include
+ icu_recipe = self.get_recipe('icu', self.ctx)
+ icu_link_libs = icu_recipe.built_libraries.keys()
+ env["PYICU_LIBRARIES"] = ":".join(lib[3:-3] for lib in icu_link_libs)
+ env["CPPFLAGS"] += " -I" + icu_include
+ env["LDFLAGS"] += " -L" + join(
+ icu_recipe.get_build_dir(arch.arch), "icu_build", "lib"
+ )
- include = (
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/include/"
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/"
- "{arch}/include")
- include = include.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["CC"] += include
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["LDFLAGS"] += " -lgnustl_shared -L"+lib
-
- build_dir = self.get_build_dir(arch.arch)
- env["LDFLAGS"] += " -L"+build_dir
return env
- def build_arch(self, arch):
- build_dir = self.get_build_dir(arch.arch)
-
- info("create links to icu libs")
- lib_dir = join(self.ctx.get_python_install_dir(), "lib")
- icu_libs = [f for f in os.listdir(lib_dir) if f.startswith("libicu")]
-
- for l in icu_libs:
- raw = l.rsplit(".", 1)[0]
- try:
- shprint(sh.ln, "-s", join(lib_dir, l), join(build_dir, raw))
- except Exception:
- pass
-
- super(PyICURecipe, self).build_arch(arch)
-
recipe = PyICURecipe()
diff --git a/p4a/pythonforandroid/recipes/pyicu/icu.patch b/p4a/pythonforandroid/recipes/pyicu/icu.patch
deleted file mode 100644
index e0a42fc..0000000
--- a/p4a/pythonforandroid/recipes/pyicu/icu.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-diff -Naur icu.py icu1.py
---- pyicu/icu.py 2012-11-23 21:28:55.000000000 +0100
-+++ icu1.py 2016-05-14 14:45:44.160023949 +0200
-@@ -34,4 +34,15 @@
- class InvalidArgsError(Exception):
- pass
-
-+import ctypes
-+import os
-+root = os.environ["ANDROID_APP_PATH"]
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libgnustl_shared.so"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicudata.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicuuc.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicui18n.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicule.so.57"))
-+del root
-+del os
-+
- from docs import *
diff --git a/p4a/pythonforandroid/recipes/pyjnius/__init__.py b/p4a/pythonforandroid/recipes/pyjnius/__init__.py
index 8aeac6c..58103e2 100644
--- a/p4a/pythonforandroid/recipes/pyjnius/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyjnius/__init__.py
@@ -6,19 +6,22 @@ from os.path import join
class PyjniusRecipe(CythonRecipe):
- # "6553ad4" is one commit after last release (1.2.0)
- # it fixes method resolution, required for resolving requestPermissions()
- version = '6553ad4'
+ version = '1.4.2'
url = 'https://github.com/kivy/pyjnius/archive/{version}.zip'
name = 'pyjnius'
- depends = [('genericndkbuild', 'sdl2', 'sdl'), 'six']
+ depends = [('genericndkbuild', 'sdl2'), 'six']
site_packages_name = 'jnius'
- patches = [('sdl2_jnienv_getter.patch', will_build('sdl2')),
- ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))]
+ patches = [('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))]
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
+ return env
def postbuild_arch(self, arch):
- super(PyjniusRecipe, self).postbuild_arch(arch)
+ super().postbuild_arch(arch)
info('Copying pyjnius java class to classes build dir')
with current_directory(self.get_build_dir(arch.arch)):
shprint(sh.cp, '-a', join('jnius', 'src', 'org'), self.ctx.javaclass_dir)
diff --git a/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch b/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
index ff26994..fcd5387 100644
--- a/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
+++ b/p4a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
@@ -1,25 +1,24 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
+diff -Naur pyjnius.orig/jnius/env.py pyjnius/jnius/env.py
+--- pyjnius.orig/jnius/env.py 2022-05-28 11:16:02.000000000 +0200
++++ pyjnius/jnius/env.py 2022-05-28 11:18:30.000000000 +0200
+@@ -268,7 +268,7 @@
+
+ class AndroidJavaLocation(UnixJavaLocation):
+ def get_libraries(self):
+- return ['SDL2', 'log']
++ return ['main', 'log']
+
+ def get_include_dirs(self):
+ # When cross-compiling for Android, we should not use the include dirs
+diff -Naur pyjnius.orig/jnius/jnius_jvm_android.pxi pyjnius/jnius/jnius_jvm_android.pxi
+--- pyjnius.orig/jnius/jnius_jvm_android.pxi 2022-05-28 11:16:02.000000000 +0200
++++ pyjnius/jnius/jnius_jvm_android.pxi 2022-05-28 11:17:17.000000000 +0200
+@@ -1,6 +1,6 @@
# on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
+-cdef extern JNIEnv *SDL_AndroidGetJNIEnv()
+cdef extern JNIEnv *WebView_AndroidGetJNIEnv()
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return WebView_AndroidGetJNIEnv()
-diff --git a/setup.py b/setup.py
-index 740510f..0c8e55f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -53,7 +53,7 @@ except ImportError:
- if PLATFORM == 'android':
- # for android, we use SDL...
-- LIBRARIES = ['sdl', 'log']
-+ LIBRARIES = ['main', 'log']
- LIBRARY_DIRS = ['libs/' + getenv('ARCH')]
- elif PLATFORM == 'darwin':
- import subprocess
+ cdef JNIEnv *get_platform_jnienv() except NULL:
+- return SDL_AndroidGetJNIEnv()
++ return WebView_AndroidGetJNIEnv()
diff --git a/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch b/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch
deleted file mode 100644
index d208e5f..0000000
--- a/p4a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
- # on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
-+cdef extern JNIEnv *SDL_AndroidGetJNIEnv()
-
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return SDL_AndroidGetJNIEnv()
-diff --git a/setup.py b/setup.py
-index 740510f..0c8e55f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -53,7 +53,7 @@ except ImportError:
-
- if PLATFORM == 'android':
- # for android, we use SDL...
-- 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 6147709..54dfb64 100644
--- a/p4a/pythonforandroid/recipes/pyleveldb/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyleveldb/__init__.py
@@ -2,12 +2,26 @@ from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
class PyLevelDBRecipe(CppCompiledComponentsPythonRecipe):
- version = '0.193'
- url = 'https://pypi.python.org/packages/source/l/leveldb/leveldb-{version}.tar.gz'
- depends = ['snappy', 'leveldb', ('hostpython2', 'hostpython3'), 'setuptools']
+ version = '0.194'
+ url = ('https://pypi.python.org/packages/source/l/leveldb/'
+ 'leveldb-{version}.tar.gz')
+ depends = ['snappy', 'leveldb', 'setuptools']
patches = ['bindings-only.patch']
- call_hostpython_via_targetpython = False # Due to setuptools
site_packages_name = 'leveldb'
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ snappy_recipe = self.get_recipe('snappy', self.ctx)
+ leveldb_recipe = self.get_recipe('leveldb', self.ctx)
+
+ env["LDFLAGS"] += " -L" + snappy_recipe.get_build_dir(arch.arch)
+ env["LDFLAGS"] += " -L" + leveldb_recipe.get_build_dir(arch.arch)
+
+ env["SNAPPY_BUILD_PATH"] = snappy_recipe.get_build_dir(arch.arch)
+ env["LEVELDB_BUILD_PATH"] = leveldb_recipe.get_build_dir(arch.arch)
+
+ return env
+
recipe = PyLevelDBRecipe()
diff --git a/p4a/pythonforandroid/recipes/pyleveldb/bindings-only.patch b/p4a/pythonforandroid/recipes/pyleveldb/bindings-only.patch
index 2899f4e..9f7027a 100644
--- a/p4a/pythonforandroid/recipes/pyleveldb/bindings-only.patch
+++ b/p4a/pythonforandroid/recipes/pyleveldb/bindings-only.patch
@@ -1,103 +1,119 @@
---- pyleveldb/setup.py 2014-03-28 02:51:24.000000000 +0100
-+++ pyleveldb-patch/setup.py 2016-03-02 11:52:13.780678586 +0100
-@@ -7,41 +7,22 @@
- #
- # See LICENSE for details.
-
--import glob
--import platform
--import sys
--
+This patch force to only build the python bindings, and to do so, we modify
+the setup.py file in oder that finds our compiled libraries (libleveldb.so and
+libsnappy.so)
+--- leveldb-0.194/setup.py.orig 2016-09-17 02:05:55.000000000 +0200
++++ leveldb-0.194/setup.py 2019-02-26 16:57:40.997435911 +0100
+@@ -11,44 +11,25 @@ import platform
+ import sys
+
from setuptools import setup, Extension
-
--system,node,release,version,machine,processor = platform.uname()
++from os import environ
+
+ system, node, release, version, machine, processor = platform.uname()
-common_flags = [
+- '-I./leveldb/include',
+- '-I./leveldb',
+- '-I./snappy',
+extra_compile_args = [
- '-I./leveldb/include',
- '-I./leveldb',
-- '-I./snappy',
-+ '-I./leveldb/snappy',
- '-I.',
-- '-fno-builtin-memcmp',
- '-O2',
- '-fPIC',
- '-DNDEBUG',
- '-DSNAPPY',
--]
--
++ '-I{}/include'.format(environ.get('LEVELDB_BUILD_PATH')),
++ '-I{}'.format(environ.get('LEVELDB_BUILD_PATH')),
++ '-I{}'.format(environ.get('SNAPPY_BUILD_PATH')),
++ '-I.',
+ '-I.',
+- '-fno-builtin-memcmp',
+ '-O2',
+ '-fPIC',
+ '-DNDEBUG',
+ '-DSNAPPY',
++ '-pthread',
++ '-Wall',
++ '-D_REENTRANT',
++ '-DOS_ANDROID',
+ ]
+
-if system == 'Darwin':
-- extra_compile_args = common_flags + [
-- '-DOS_MACOSX',
-+ '-Wall',
- '-DLEVELDB_PLATFORM_POSIX',
-- '-Wno-error=unused-command-line-argument-hard-error-in-future',
-- ]
+- extra_compile_args = common_flags + [
+- '-DOS_MACOSX',
+- '-DLEVELDB_PLATFORM_POSIX',
+- '-Wno-error=unused-command-line-argument-hard-error-in-future',
+- ]
-elif system == 'Linux':
+- extra_compile_args = common_flags + [
+- '-pthread',
+- '-Wall',
+- '-DOS_LINUX',
+- '-DLEVELDB_PLATFORM_POSIX',
+- ]
+-elif system == 'SunOS':
- extra_compile_args = common_flags + [
- '-pthread',
-- '-Wall',
-- '-DOS_LINUX',
+- '-Wall',
+- '-DOS_SOLARIS',
- '-DLEVELDB_PLATFORM_POSIX',
- ]
-else:
-- print >>sys.stderr, "Don't know how to compile leveldb for %s!" % system
-- sys.exit(0)
-+ '-D_REENTRANT',
-+ '-DOS_ANDROID',
-+]
-
+- sys.stderr.write("Don't know how to compile leveldb for %s!\n" % system)
+- sys.exit(1)
+-
setup(
- name = 'leveldb',
-@@ -75,52 +56,6 @@
- ext_modules = [
- Extension('leveldb',
- sources = [
-- # snappy
-- './snappy/snappy.cc',
-- './snappy/snappy-stubs-internal.cc',
-- './snappy/snappy-sinksource.cc',
-- './snappy/snappy-c.cc',
+ name = 'leveldb',
+ version = '0.194',
+@@ -81,57 +62,11 @@ setup(
+ ext_modules = [
+ Extension('leveldb',
+ sources = [
+- # snappy
+- './snappy/snappy.cc',
+- './snappy/snappy-stubs-internal.cc',
+- './snappy/snappy-sinksource.cc',
+- './snappy/snappy-c.cc',
-
-- #leveldb
-- 'leveldb/db/builder.cc',
-- 'leveldb/db/c.cc',
-- 'leveldb/db/db_impl.cc',
-- 'leveldb/db/db_iter.cc',
-- 'leveldb/db/dbformat.cc',
-- 'leveldb/db/filename.cc',
-- 'leveldb/db/log_reader.cc',
-- 'leveldb/db/log_writer.cc',
-- 'leveldb/db/memtable.cc',
-- 'leveldb/db/repair.cc',
-- 'leveldb/db/table_cache.cc',
-- 'leveldb/db/version_edit.cc',
-- 'leveldb/db/version_set.cc',
-- 'leveldb/db/write_batch.cc',
-- 'leveldb/table/block.cc',
-- 'leveldb/table/block_builder.cc',
-- 'leveldb/table/filter_block.cc',
-- 'leveldb/table/format.cc',
-- 'leveldb/table/iterator.cc',
-- 'leveldb/table/merger.cc',
-- 'leveldb/table/table.cc',
-- 'leveldb/table/table_builder.cc',
-- 'leveldb/table/two_level_iterator.cc',
-- 'leveldb/util/arena.cc',
-- 'leveldb/util/bloom.cc',
-- 'leveldb/util/cache.cc',
-- 'leveldb/util/coding.cc',
-- 'leveldb/util/comparator.cc',
-- 'leveldb/util/crc32c.cc',
-- 'leveldb/util/env.cc',
-- 'leveldb/util/env_posix.cc',
-- 'leveldb/util/filter_policy.cc',
-- 'leveldb/util/hash.cc',
-- 'leveldb/util/histogram.cc',
-- 'leveldb/util/logging.cc',
-- 'leveldb/util/options.cc',
-- 'leveldb/util/status.cc',
-- 'leveldb/port/port_posix.cc',
+- #leveldb
+- 'leveldb/db/builder.cc',
+- 'leveldb/db/c.cc',
+- 'leveldb/db/db_impl.cc',
+- 'leveldb/db/db_iter.cc',
+- 'leveldb/db/dbformat.cc',
+- 'leveldb/db/filename.cc',
+- 'leveldb/db/log_reader.cc',
+- 'leveldb/db/log_writer.cc',
+- 'leveldb/db/memtable.cc',
+- 'leveldb/db/repair.cc',
+- 'leveldb/db/table_cache.cc',
+- 'leveldb/db/version_edit.cc',
+- 'leveldb/db/version_set.cc',
+- 'leveldb/db/write_batch.cc',
+- 'leveldb/table/block.cc',
+- 'leveldb/table/block_builder.cc',
+- 'leveldb/table/filter_block.cc',
+- 'leveldb/table/format.cc',
+- 'leveldb/table/iterator.cc',
+- 'leveldb/table/merger.cc',
+- 'leveldb/table/table.cc',
+- 'leveldb/table/table_builder.cc',
+- 'leveldb/table/two_level_iterator.cc',
+- 'leveldb/util/arena.cc',
+- 'leveldb/util/bloom.cc',
+- 'leveldb/util/cache.cc',
+- 'leveldb/util/coding.cc',
+- 'leveldb/util/comparator.cc',
+- 'leveldb/util/crc32c.cc',
+- 'leveldb/util/env.cc',
+- 'leveldb/util/env_posix.cc',
+- 'leveldb/util/filter_policy.cc',
+- 'leveldb/util/hash.cc',
+- 'leveldb/util/histogram.cc',
+- 'leveldb/util/logging.cc',
+- 'leveldb/util/options.cc',
+- 'leveldb/util/status.cc',
+- 'leveldb/port/port_posix.cc',
-
- # python stuff
- 'leveldb_ext.cc',
- 'leveldb_object.cc',
+ # python stuff
+ 'leveldb_ext.cc',
+ 'leveldb_object.cc',
+ ],
+- libraries = ['stdc++'],
++ libraries = ['snappy', 'leveldb', 'stdc++', 'c++_shared'],
+ extra_compile_args = extra_compile_args,
+ )
+ ]
diff --git a/p4a/pythonforandroid/recipes/pymunk/__init__.py b/p4a/pythonforandroid/recipes/pymunk/__init__.py
index b72b85b..a982098 100644
--- a/p4a/pythonforandroid/recipes/pymunk/__init__.py
+++ b/p4a/pythonforandroid/recipes/pymunk/__init__.py
@@ -1,21 +1,17 @@
-from os.path import join
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class PymunkRecipe(CompiledComponentsPythonRecipe):
name = "pymunk"
- version = '5.3.2'
- url = 'https://pypi.python.org/packages/source/p/pymunk/pymunk-{version}.zip'
- depends = ['cffi', 'setuptools']
+ version = "6.0.0"
+ 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()
- env['LDFLAGS'] += " -shared -llog"
- 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'
+ env = super().get_recipe_env(arch)
+ env["LDFLAGS"] += " -llog" # Used by Chipmunk cpMessage
+ env["LDFLAGS"] += " -lm" # For older versions of Android
return env
diff --git a/p4a/pythonforandroid/recipes/pynacl/__init__.py b/p4a/pythonforandroid/recipes/pynacl/__init__.py
index eb9ca2d..0ab9352 100644
--- a/p4a/pythonforandroid/recipes/pynacl/__init__.py
+++ b/p4a/pythonforandroid/recipes/pynacl/__init__.py
@@ -7,11 +7,11 @@ class PyNaCLRecipe(CompiledComponentsPythonRecipe):
version = '1.3.0'
url = 'https://pypi.python.org/packages/source/P/PyNaCl/PyNaCl-{version}.tar.gz'
- depends = [('hostpython2', 'hostpython3'), 'six', 'setuptools', 'cffi', 'libsodium']
+ depends = ['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 = super().get_recipe_env(arch)
env['SODIUM_INSTALL'] = 'system'
libsodium_build_dir = self.get_recipe(
diff --git a/p4a/pythonforandroid/recipes/pyproj/__init__.py b/p4a/pythonforandroid/recipes/pyproj/__init__.py
index 71b272d..0c47b29 100644
--- a/p4a/pythonforandroid/recipes/pyproj/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyproj/__init__.py
@@ -2,8 +2,8 @@ from pythonforandroid.recipe import CythonRecipe
class PyProjRecipe(CythonRecipe):
- version = '1.9.5.1'
- url = 'https://github.com/jswhit/pyproj/archive/master.zip'
+ version = '1.9.6'
+ url = 'https://github.com/pyproj4/pyproj/archive/v{version}rel.zip'
depends = ['setuptools']
call_hostpython_via_targetpython = False
diff --git a/p4a/pythonforandroid/recipes/pysdl2/__init__.py b/p4a/pythonforandroid/recipes/pysdl2/__init__.py
index e0df9dc..b1dc9cb 100644
--- a/p4a/pythonforandroid/recipes/pysdl2/__init__.py
+++ b/p4a/pythonforandroid/recipes/pysdl2/__init__.py
@@ -1,10 +1,9 @@
-
from pythonforandroid.recipe import PythonRecipe
class PySDL2Recipe(PythonRecipe):
- version = '0.9.3'
- url = 'https://bitbucket.org/marcusva/py-sdl2/downloads/PySDL2-{version}.tar.gz'
+ version = '0.9.6'
+ url = 'https://files.pythonhosted.org/packages/source/P/PySDL2/PySDL2-{version}.tar.gz'
depends = ['sdl2']
diff --git a/p4a/pythonforandroid/recipes/pysha3/__init__.py b/p4a/pythonforandroid/recipes/pysha3/__init__.py
index 35cfff8..af38946 100644
--- a/p4a/pythonforandroid/recipes/pysha3/__init__.py
+++ b/p4a/pythonforandroid/recipes/pysha3/__init__.py
@@ -10,19 +10,14 @@ class Pysha3Recipe(PythonRecipe):
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(Pysha3Recipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().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'] = env['LDFLAGS'].replace('-lm', '')
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
diff --git a/p4a/pythonforandroid/recipes/python2/__init__.py b/p4a/pythonforandroid/recipes/python2/__init__.py
deleted file mode 100644
index beba2b6..0000000
--- a/p4a/pythonforandroid/recipes/python2/__init__.py
+++ /dev/null
@@ -1,74 +0,0 @@
-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(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', '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-posix-declarations.patch',
- 'patches/fix-pwd-gecos.patch']
-
- 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}')
-
- compiled_extension = '.pyo'
-
- 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)
-
- 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',)
-
- 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
deleted file mode 100644
index 490e065..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/enable-openssl.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- 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
deleted file mode 100644
index 73dfc98..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-api-minor-than-21.patch
+++ /dev/null
@@ -1,229 +0,0 @@
-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
deleted file mode 100644
index 7cf1a8f..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-filesystem-default-encoding.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- 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
deleted file mode 100644
index a3824f4..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- 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
-+
- #ifndef linux
- # undef HAVE_GETHOSTBYNAME_R_3_ARG
- # undef HAVE_GETHOSTBYNAME_R_5_ARG
diff --git a/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch b/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch
deleted file mode 100644
index a098b25..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-missing-extensions.patch
+++ /dev/null
@@ -1,120 +0,0 @@
-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
deleted file mode 100644
index c2a8049..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-posix-declarations.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- 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
deleted file mode 100644
index cdc06fd..0000000
--- a/p4a/pythonforandroid/recipes/python2/patches/fix-pwd-gecos.patch
+++ /dev/null
@@ -1,89 +0,0 @@
---- 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/python2legacy/Setup.local-ssl b/p4a/pythonforandroid/recipes/python2legacy/Setup.local-ssl
deleted file mode 100644
index eadc6ea..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/Setup.local-ssl
+++ /dev/null
@@ -1,4 +0,0 @@
-SSL=
-_ssl _ssl.c \
- -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
- -L$(SSL) -lssl$(OPENSSL_VERSION) -lcrypto$(OPENSSL_VERSION)
diff --git a/p4a/pythonforandroid/recipes/python2legacy/__init__.py b/p4a/pythonforandroid/recipes/python2legacy/__init__.py
deleted file mode 100644
index fc0de88..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/__init__.py
+++ /dev/null
@@ -1,231 +0,0 @@
-from pythonforandroid.recipe import TargetPythonRecipe, Recipe
-from pythonforandroid.toolchain import shprint, current_directory, info
-from pythonforandroid.patching import (is_darwin, is_api_gt,
- check_all, is_api_lt, is_ndk)
-from multiprocessing import cpu_count
-from os.path import exists, join, realpath
-from os import walk
-import glob
-import sh
-
-EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx")
-
-
-class Python2LegacyRecipe(TargetPythonRecipe):
- '''
- .. warning:: This python2 recipe is the original one created by @tito and,
- for now, it is unusable.
-
- .. versionchanged:: 0.6.0
- This was the original python2's recipe moved to python2legacy.
- '''
- version = "2.7.2"
- url = 'https://python.org/ftp/python/{version}/Python-{version}.tar.bz2'
- name = 'python2legacy'
-
- depends = ['hostpython2legacy']
- conflicts = ['python3', 'python3crystax', 'python2']
- 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',
- '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')))]
-
- from_crystax = False
-
- def prebuild_arch(self, arch):
- super(Python2LegacyRecipe, 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)
-
- def build_arch(self, arch):
-
- if not exists(join(self.get_build_dir(arch.arch), 'libpython2.7.so')):
- self.do_python_build(arch)
-
- 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())
-
- # 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))
-
- def do_python_build(self, arch):
-
- 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('hostpython2legacy', self.ctx)
- shprint(sh.cp, join(hostpython_recipe.get_recipe_dir(), 'Setup'), 'Modules')
-
- env = arch.get_env()
-
- 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:
- 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
-
- if 'sqlite3' in self.ctx.recipe_build_order:
- # Include sqlite3 in python2legacy build
- recipe = Recipe.get_recipe('sqlite3', self.ctx)
- include = ' -I' + recipe.get_build_dir(arch.arch)
- lib = ' -L' + recipe.get_lib_dir(arch) + ' -lsqlite3'
- # Insert or append to env
- flag = 'CPPFLAGS'
- env[flag] = env[flag] + include if flag in env else include
- flag = 'LDFLAGS'
- env[flag] = env[flag] + lib if flag in env else lib
-
- # NDK has langinfo.h but doesn't define nl_langinfo()
- env['ac_cv_header_langinfo_h'] = 'no'
- configure = sh.Command('./configure')
- 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)
-
- # 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 # noqa
- # /home/tito/code/python-for-android/build/python/Python-2.7.2/python: 1: Syntax error: word unexpected (expecting ")") # noqa
- # 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, '-j' + str(cpu_count()), '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, '-j' + str(cpu_count()), '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))
-
- def create_python_install(self, dist_dir):
- hostpython = sh.Command(self.ctx.hostpython)
- install_dir = self.ctx.get_python_install_dir()
- with current_directory(dist_dir):
- try:
- shprint(hostpython, '-OO', '-m', 'compileall',
- install_dir,
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(sh.cp, '-a', install_dir, './python-install')
-
- def create_python_bundle(self, dirn, arch):
- info("Filling private directory")
- if not exists(join(dirn, "lib")):
- info("lib dir does not exist, making")
- shprint(sh.cp, "-a",
- join("python-install", "lib"), dirn)
- shprint(sh.mkdir, "-p",
- join(dirn, "include", "python2.7"))
-
- libpymodules_fn = join("libs", arch.arch, "libpymodules.so")
- if exists(libpymodules_fn):
- shprint(sh.mv, libpymodules_fn, dirn)
- shprint(sh.cp,
- join('python-install', 'include',
- 'python2.7', 'pyconfig.h'),
- join(dirn, 'include', 'python2.7/'))
-
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join(dirn, 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join(dirn, 'lib', 'pkgconfig'))
-
- libdir = join(dirn, 'lib', 'python2.7')
- site_packages_dir = join(libdir, 'site-packages')
- with current_directory(libdir):
- removes = []
- for dirname, root, filenames in walk("."):
- for filename in filenames:
- if filename.endswith(EXCLUDE_EXTS):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
-
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- shprint(sh.rm, '-f', *glob.glob('config/libpython*.a'))
- shprint(sh.rm, '-rf', 'config/python.o')
-
- return site_packages_dir
-
- def include_root(self, arch_name):
- return join(self.get_build_dir(arch_name), 'python-install', 'include', 'python2.7')
-
- def link_root(self, arch_name):
- return join(self.get_build_dir(arch_name), 'python-install', 'lib')
-
-
-recipe = Python2LegacyRecipe()
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-ctypes-disable-wchar.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-ctypes-disable-wchar.patch
deleted file mode 100644
index 19d8915..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-ctypes-disable-wchar.patch
+++ /dev/null
@@ -1,116 +0,0 @@
-diff -ur '--exclude=*~' Python-2.7.2.orig/Lib/ctypes/__init__.py Python-2.7.2/Lib/ctypes/__init__.py
---- Python-2.7.2.orig/Lib/ctypes/__init__.py 2011-06-11 16:46:24.000000000 +0100
-+++ Python-2.7.2/Lib/ctypes/__init__.py 2015-03-19 12:32:45.747723687 +0000
-@@ -272,31 +272,34 @@
- else:
- set_conversion_mode("ascii", "strict")
-
-- class c_wchar_p(_SimpleCData):
-- _type_ = "Z"
--
-- class c_wchar(_SimpleCData):
-- _type_ = "u"
--
-- POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
--
-- def create_unicode_buffer(init, size=None):
-- """create_unicode_buffer(aString) -> character array
-- create_unicode_buffer(anInteger) -> character array
-- create_unicode_buffer(aString, anInteger) -> character array
-- """
-- if isinstance(init, (str, unicode)):
-- if size is None:
-- size = len(init)+1
-- buftype = c_wchar * size
-- buf = buftype()
-- buf.value = init
-- return buf
-- elif isinstance(init, (int, long)):
-- buftype = c_wchar * init
-- buf = buftype()
-- return buf
-- raise TypeError(init)
-+# The wchar stuff causes a crash on Android (the bionic C library doesn't
-+# implement wchar_t anyway)
-+#
-+# class c_wchar_p(_SimpleCData):
-+# _type_ = "Z"
-+#
-+# class c_wchar(_SimpleCData):
-+# _type_ = "u"
-+#
-+# POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
-+#
-+# def create_unicode_buffer(init, size=None):
-+# """create_unicode_buffer(aString) -> character array
-+# create_unicode_buffer(anInteger) -> character array
-+# create_unicode_buffer(aString, anInteger) -> character array
-+# """
-+# if isinstance(init, (str, unicode)):
-+# if size is None:
-+# size = len(init)+1
-+# buftype = c_wchar * size
-+# buf = buftype()
-+# buf.value = init
-+# return buf
-+# elif isinstance(init, (int, long)):
-+# buftype = c_wchar * init
-+# buf = buftype()
-+# return buf
-+# raise TypeError(init)
-
- POINTER(c_char).from_param = c_char_p.from_param #_SimpleCData.c_char_p_from_param
-
-diff -ur '--exclude=*~' Python-2.7.2.orig/Modules/_ctypes/callproc.c Python-2.7.2/Modules/_ctypes/callproc.c
---- Python-2.7.2.orig/Modules/_ctypes/callproc.c 2015-03-19 11:56:40.668159317 +0000
-+++ Python-2.7.2/Modules/_ctypes/callproc.c 2015-03-19 11:45:45.898288000 +0000
-@@ -1423,7 +1423,7 @@
- mode |= RTLD_NOW;
- handle = ctypes_dlopen(name, mode);
- if (!handle) {
-- char *errmsg = ctypes_dlerror();
-+ const char *errmsg = ctypes_dlerror();
- if (!errmsg)
- errmsg = "dlopen() error";
- PyErr_SetString(PyExc_OSError,
-diff -ur '--exclude=*~' Python-2.7.2.orig/Modules/_ctypes/libffi/src/dlmalloc.c Python-2.7.2/Modules/_ctypes/libffi/src/dlmalloc.c
---- Python-2.7.2.orig/Modules/_ctypes/libffi/src/dlmalloc.c 2015-03-19 13:26:58.928438829 +0000
-+++ Python-2.7.2/Modules/_ctypes/libffi/src/dlmalloc.c 2015-03-19 15:32:19.042396376 +0000
-@@ -614,18 +614,6 @@
- #include "/usr/include/malloc.h"
- #else /* HAVE_USR_INCLUDE_MALLOC_H */
-
--struct mallinfo {
-- MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */
-- MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */
-- MALLINFO_FIELD_TYPE smblks; /* always 0 */
-- MALLINFO_FIELD_TYPE hblks; /* always 0 */
-- MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */
-- MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */
-- MALLINFO_FIELD_TYPE fsmblks; /* always 0 */
-- MALLINFO_FIELD_TYPE uordblks; /* total allocated space */
-- MALLINFO_FIELD_TYPE fordblks; /* total free space */
-- MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */
--};
-
- #endif /* HAVE_USR_INCLUDE_MALLOC_H */
- #endif /* NO_MALLINFO */
-@@ -966,7 +954,7 @@
- p = malloc(n);
- assert(malloc_usable_size(p) >= 256);
- */
--size_t dlmalloc_usable_size(void*);
-+size_t dlmalloc_usable_size(const void*);
-
- /*
- malloc_stats();
-@@ -4384,7 +4372,7 @@
- internal_malloc_stats(gm);
- }
-
--size_t dlmalloc_usable_size(void* mem) {
-+size_t dlmalloc_usable_size(const void* mem) {
- if (mem != 0) {
- mchunkptr p = mem2chunk(mem);
- if (cinuse(p))
-
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch
deleted file mode 100644
index 9883cb5..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch
+++ /dev/null
@@ -1,205 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
---- Python-2.6.6.orig//Lib/plat-linux3/regen 1970-01-01 01:00:00.000000000 +0100
-+++ Python-2.6.6/Lib/plat-linux3/regen 2001-08-09 14:48:17.000000000 +0200
-@@ -0,0 +1,8 @@
-+#! /bin/sh
-+case `uname` in
-+Linux*) ;;
-+*) echo Probably not on a Linux system 1>&2
-+ exit 1;;
-+esac
-+set -v
-+h2py -i '(u_long)' /usr/include/sys/types.h /usr/include/netinet/in.h /usr/include/dlfcn.h
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-backup b/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-backup
deleted file mode 100644
index 3fdb85d..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-backup
+++ /dev/null
@@ -1,194 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-new b/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-new
deleted file mode 100644
index 9883cb5..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/Python-2.7.2-xcompile.patch-new
+++ /dev/null
@@ -1,205 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
---- Python-2.6.6.orig//Lib/plat-linux3/regen 1970-01-01 01:00:00.000000000 +0100
-+++ Python-2.6.6/Lib/plat-linux3/regen 2001-08-09 14:48:17.000000000 +0200
-@@ -0,0 +1,8 @@
-+#! /bin/sh
-+case `uname` in
-+Linux*) ;;
-+*) echo Probably not on a Linux system 1>&2
-+ exit 1;;
-+esac
-+set -v
-+h2py -i '(u_long)' /usr/include/sys/types.h /usr/include/netinet/in.h /usr/include/dlfcn.h
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/_scproxy.py b/p4a/pythonforandroid/recipes/python2legacy/patches/_scproxy.py
deleted file mode 100644
index c481f6d..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/_scproxy.py
+++ /dev/null
@@ -1,12 +0,0 @@
-'''
-Stub functions for _scproxy on iOS
-No proxy is supported yet.
-'''
-
-
-def _get_proxy_settings():
- return {'exclude_simple': 1}
-
-
-def _get_proxies():
- return {}
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library-updated.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library-updated.patch
deleted file mode 100644
index 07221cc..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library-updated.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
-index 52b3520..01b13a9 100644
---- a/Lib/ctypes/util.py
-+++ b/Lib/ctypes/util.py
-@@ -71,7 +71,23 @@ if os.name == "ce":
- def find_library(name):
- return name
-
--if os.name == "posix" and sys.platform == "darwin":
-+# This patch overrides the find_library to look in the right places on
-+# Android
-+if True:
-+ def find_library(name):
-+ # Check the user app lib dir
-+ app_root = os.path.abspath('../../').split(os.path.sep)
-+ lib_search = os.path.sep.join(app_root) + os.path.sep + 'lib'
-+ for filename in os.listdir(lib_search):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ # Check the normal Android system libraries
-+ for filename in os.listdir('/system/lib'):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ return None
-+
-+elif os.name == "posix" and sys.platform == "darwin":
- from ctypes.macholib.dyld import dyld_find as _dyld_find
- def find_library(name):
- possible = ['lib%s.dylib' % name,
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library.patch
deleted file mode 100644
index 791e042..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/ctypes-find-library.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff -ur Python-2.7.2.orig/Lib/ctypes/util.py Python-2.7.2/Lib/ctypes/util.py
---- Python-2.7.2.orig/Lib/ctypes/util.py 2011-06-11 16:46:24.000000000 +0100
-+++ Python-2.7.2/Lib/ctypes/util.py 2015-05-10 15:50:18.906203529 +0100
-@@ -71,7 +71,21 @@
- def find_library(name):
- return name
-
--if os.name == "posix" and sys.platform == "darwin":
-+# this test is for android specifically shoudl match here and ignore any
-+# of the other platform tests below
-+if os.name == "posix":
-+ def find_library(name):
-+ """ hack to find librarys for kivy and android
-+ split the path and get the first parts which will give us
-+ the app path something like /data/data/org.app.foo/"""
-+ app_root = os.path.abspath('../../').split(os.path.sep)
-+ lib_search = os.path.sep.join(app_root) + os.path.sep + 'lib'
-+ for filename in os.listdir(lib_search):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ return None
-+
-+elif os.name == "posix" and sys.platform == "darwin":
- from ctypes.macholib.dyld import dyld_find as _dyld_find
- def find_library(name):
- possible = ['lib%s.dylib' % name,
-Only in Python-2.7.2/Lib/ctypes: util.py.save
-Only in Python-2.7.2/Lib/ctypes: util.py.save.1
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/custom-loader.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/custom-loader.patch
deleted file mode 100644
index 97dc280..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/custom-loader.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- Python-2.7.2.orig/Python/dynload_shlib.c 2010-05-09 16:46:46.000000000 +0200
-+++ Python-2.7.2/Python/dynload_shlib.c 2011-04-20 17:52:12.000000000 +0200
-@@ -6,6 +6,7 @@
-
- #include
- #include
-+#include
-
- #if defined(__NetBSD__)
- #include
-@@ -75,6 +76,21 @@
- char pathbuf[260];
- int dlopenflags=0;
-
-+ static void *libpymodules = -1;
-+ void *rv = NULL;
-+
-+ /* Ensure we have access to libpymodules. */
-+ if (libpymodules == -1) {
-+ printf("ANDROID_UNPACK = %s\n", getenv("ANDROID_UNPACK"));
-+ PyOS_snprintf(pathbuf, sizeof(pathbuf), "%s/libpymodules.so", getenv("ANDROID_UNPACK"));
-+ libpymodules = dlopen(pathbuf, RTLD_NOW);
-+
-+ if (libpymodules == NULL) {
-+ //abort();
-+ }
-+ }
-+
-+
- if (strchr(pathname, '/') == NULL) {
- /* Prefix bare filename with "./" */
- PyOS_snprintf(pathbuf, sizeof(pathbuf), "./%-.255s", pathname);
-@@ -84,6 +100,17 @@
- PyOS_snprintf(funcname, sizeof(funcname),
- LEAD_UNDERSCORE "init%.200s", shortname);
-
-+
-+ /* Read symbols that have been linked into the main binary. */
-+
-+ if (libpymodules) {
-+ rv = dlsym(libpymodules, funcname);
-+ if (rv != NULL) {
-+ return rv;
-+ }
-+ }
-+
-+
- if (fp != NULL) {
- int i;
- struct stat statb;
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/disable-modules.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/disable-modules.patch
deleted file mode 100644
index 3841488..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/disable-modules.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2.orig/setup.py 2010-10-31 17:40:21.000000000 +0100
-+++ Python-2.7.2/setup.py 2011-11-27 16:49:36.840204364 +0100
-@@ -21,7 +21,7 @@
- COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
-
- # This global variable is used to hold the list of modules to be disabled.
--disabled_module_list = []
-+disabled_module_list = ['spwd','bz2','ossaudiodev','_curses','_curses_panel','readline','_locale','_bsddb','gdbm','dbm','nis','linuxaudiodev','crypt','_multiprocessing']
-
- def add_dir_to_list(dirlist, dir):
- """Add the directory 'dir' to the list 'dirlist' (at the front) if
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/disable-openpty.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/disable-openpty.patch
deleted file mode 100644
index e6f9717..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/disable-openpty.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- python2/Modules/posixmodule.c 2016-01-05 15:54:39.504651722 -0600
-+++ b/Modules/posixmodule.c 2016-01-05 16:01:48.072559330 -0600
-@@ -3743,54 +3743,8 @@
- static PyObject *
- posix_openpty(PyObject *self, PyObject *noargs)
- {
-- int master_fd, slave_fd;
--#ifndef HAVE_OPENPTY
-- char * slave_name;
--#endif
--#if defined(HAVE_DEV_PTMX) && !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY)
-- PyOS_sighandler_t sig_saved;
--#ifdef sun
-- extern char *ptsname(int fildes);
--#endif
--#endif
--
--#ifdef HAVE_OPENPTY
-- if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
-- return posix_error();
--#elif defined(HAVE__GETPTY)
-- slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
-- if (slave_name == NULL)
-- return posix_error();
--
-- slave_fd = open(slave_name, O_RDWR);
-- if (slave_fd < 0)
-- return posix_error();
--#else
-- master_fd = open(DEV_PTY_FILE, O_RDWR | O_NOCTTY); /* open master */
-- if (master_fd < 0)
-- return posix_error();
-- sig_saved = PyOS_setsig(SIGCHLD, SIG_DFL);
-- /* change permission of slave */
-- if (grantpt(master_fd) < 0) {
-- PyOS_setsig(SIGCHLD, sig_saved);
-- return posix_error();
-- }
-- /* unlock slave */
-- if (unlockpt(master_fd) < 0) {
-- PyOS_setsig(SIGCHLD, sig_saved);
-- return posix_error();
-- }
-- PyOS_setsig(SIGCHLD, sig_saved);
-- slave_name = ptsname(master_fd); /* get name of slave */
-- if (slave_name == NULL)
-- return posix_error();
-- slave_fd = open(slave_name, O_RDWR | O_NOCTTY); /* open slave */
-- if (slave_fd < 0)
-- return posix_error();
--#endif /* HAVE_OPENPTY */
--
-- return Py_BuildValue("(ii)", master_fd, slave_fd);
--
-+ PyErr_SetString(PyExc_NotImplementedError, "openpty not implemented for this build");
-+ return NULL;
- }
- #endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX) */
-
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/enable-openssl.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/enable-openssl.patch
deleted file mode 100644
index b9e7702..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/enable-openssl.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- Python-2.7.2/setup.py.orig 2011-06-11 17:46:28.000000000 +0200
-+++ Python-2.7.2/setup.py 2018-12-23 15:53:22.521709336 +0100
-@@ -706,18 +706,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):
-@@ -735,8 +732,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 sys.platform == 'darwin' and is_macosx_sdk_path(name):
-@@ -753,8 +750,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/python2legacy/patches/enable-ssl.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/enable-ssl.patch
deleted file mode 100644
index b7a249e..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/enable-ssl.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- python2/Modules/Setup.dist 2011-06-11 10:46:26.000000000 -0500
-+++ b/Modules/Setup.dist 2015-12-28 16:18:13.329648940 -0600
-@@ -211,10 +211,10 @@
-
- # Socket module helper for SSL support; you must comment out the other
- # socket line above, and possibly edit the SSL variable:
--#SSL=/usr/local/ssl
--#_ssl _ssl.c \
--# -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
--# -L$(SSL)/lib -lssl -lcrypto
-+SSL=/p4a/path/to/openssl
-+_ssl _ssl.c \
-+ -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-+ -L$(SSL) -lssl -lcrypto
-
- # The crypt module is now disabled by default because it breaks builds
- # on many systems (where -lcrypt is needed), e.g. Linux (I believe).
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-configure-darwin.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-configure-darwin.patch
deleted file mode 100644
index 93a1fe3..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-configure-darwin.patch
+++ /dev/null
@@ -1,40 +0,0 @@
---- Python-2.7.2.orig/configure 2012-07-09 23:48:02.000000000 +0200
-+++ Python-2.7.2/configure 2012-07-09 23:47:34.000000000 +0200
-@@ -4927,7 +4927,7 @@
- RUNSHARED=LD_LIBRARY_PATH=`pwd`:${LD_LIBRARY_PATH}
- INSTSONAME="$LDLIBRARY".$SOVERSION
- ;;
-- Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*)
-+ Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|Darwin*)
- LDLIBRARY='libpython$(VERSION).so'
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED=LD_LIBRARY_PATH=`pwd`:${LD_LIBRARY_PATH}
-@@ -4960,7 +4960,7 @@
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED=DLL_PATH=`pwd`:${DLL_PATH:-/atheos/sys/libs:/atheos/autolnk/lib}
- ;;
-- Darwin*)
-+ DDarwin*)
- LDLIBRARY='libpython$(VERSION).dylib'
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED='DYLD_LIBRARY_PATH=`pwd`:${DYLD_LIBRARY_PATH}'
-@@ -7625,6 +7625,9 @@
- LDSHARED='ld -b'
- fi ;;
- OSF*) LDSHARED="ld -shared -expect_unresolved \"*\"";;
-+ Darwin*|Linux*|GNU*|QNX*)
-+ LDSHARED='$(CC) -shared'
-+ LDCXXSHARED='$(CXX) -shared';;
- Darwin/1.3*)
- LDSHARED='$(CC) -bundle'
- LDCXXSHARED='$(CXX) -bundle'
-@@ -7680,9 +7683,6 @@
- fi
- fi
- ;;
-- Linux*|GNU*|QNX*)
-- LDSHARED='$(CC) -shared'
-- LDCXXSHARED='$(CXX) -shared';;
- BSD/OS*/4*)
- LDSHARED="gcc -shared"
- LDCXXSHARED="g++ -shared";;
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-distutils-darwin.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-distutils-darwin.patch
deleted file mode 100644
index 4083634..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-distutils-darwin.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- Python-2.7.2.orig/Lib/distutils/command/build_ext.py 2011-06-11 17:46:24.000000000 +0200
-+++ Python-2.7.2/Lib/distutils/command/build_ext.py 2012-08-01 18:32:13.000000000 +0200
-@@ -236,7 +236,7 @@
- # Python's library directory must be appended to library_dirs
- sysconfig.get_config_var('Py_ENABLE_SHARED')
- if ((sys.platform.startswith('linux') or sys.platform.startswith('gnu')
-- or sys.platform.startswith('sunos'))
-+ or sys.platform.startswith('sunos') or sys.platform.startswith('darwin'))
- and sysconfig.get_config_var('Py_ENABLE_SHARED')):
- if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")):
- # building third party extensions
-@@ -750,9 +750,9 @@
- # extensions, it is a reference to the original list
- return ext.libraries + [pythonlib, "m"] + extra
-
-- elif sys.platform == 'darwin':
-- # Don't use the default code below
-- return ext.libraries
-+ #elif sys.platform == 'darwin':
-+ # # Don't use the default code below
-+ # return ext.libraries
- elif sys.platform[:3] == 'aix':
- # Don't use the default code below
- return ext.libraries
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dlfcn.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dlfcn.patch
deleted file mode 100644
index 7a685ca..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dlfcn.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff -Naur Python-2.7.2.orig/Lib/plat-linux2/DLFCN.py Python-2.7.2/Lib/plat-linux2/DLFCN.py
---- Python-2.7.2.orig/Lib/plat-linux2/DLFCN.py 2011-06-11 17:46:24.000000000 +0200
-+++ Python-2.7.2/Lib/plat-linux2/DLFCN.py 2013-07-29 16:34:45.318131844 +0200
-@@ -74,10 +74,17 @@
- # Included from gnu/stubs.h
-
- # Included from bits/dlfcn.h
-+# PATCHED FOR ANDROID (the only supported symbols are):
-+# enum {
-+# RTLD_NOW = 0,
-+# RTLD_LAZY = 1,
-+# RTLD_LOCAL = 0,
-+# RTLD_GLOBAL = 2,
-+# };
- RTLD_LAZY = 0x00001
--RTLD_NOW = 0x00002
--RTLD_BINDING_MASK = 0x3
--RTLD_NOLOAD = 0x00004
--RTLD_GLOBAL = 0x00100
-+RTLD_NOW = 0x00000
-+RTLD_BINDING_MASK = 0x0
-+RTLD_NOLOAD = 0x00000
-+RTLD_GLOBAL = 0x00002
- RTLD_LOCAL = 0
--RTLD_NODELETE = 0x01000
-+RTLD_NODELETE = 0x00000
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dynamic-lookup.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dynamic-lookup.patch
deleted file mode 100644
index 982cb30..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-dynamic-lookup.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2/Makefile.pre.in.orig 2012-07-05 17:09:45.000000000 +0200
-+++ Python-2.7.2/Makefile.pre.in 2012-07-05 17:10:00.000000000 +0200
-@@ -435,7 +435,7 @@
- fi
-
- libpython$(VERSION).dylib: $(LIBRARY_OBJS)
-- $(CC) -dynamiclib -Wl,-single_module $(LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(VERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
-+ $(CC) -dynamiclib -Wl,-single_module $(LDFLAGS) -Wl,-install_name,$(prefix)/lib/libpython$(VERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
-
-
- libpython$(VERSION).sl: $(LIBRARY_OBJS)
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-filesystemdefaultencoding.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-filesystemdefaultencoding.patch
deleted file mode 100644
index c77998e..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-filesystemdefaultencoding.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2.orig/Python/bltinmodule.c 2012-03-30 01:44:57.018079845 +0200
-+++ Python-2.7.2/Python/bltinmodule.c 2012-03-30 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/python2legacy/patches/fix-ftime-removal.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-ftime-removal.patch
deleted file mode 100644
index 5d79a1d..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-ftime-removal.patch
+++ /dev/null
@@ -1,11 +0,0 @@
-diff -u -r Python-2.7.2.orig/Modules/timemodule.c Python-2.7.2/Modules/timemodule.c
---- Python-2.7.2.orig/Modules/timemodule.c 2011-06-11 15:46:27.000000000 +0000
-+++ Python-2.7.2/Modules/timemodule.c 2015-09-11 10:37:36.708661691 +0000
-@@ -27,6 +27,7 @@
- #include
- #endif
-
-+#undef HAVE_FTIME
- #ifdef HAVE_FTIME
- #include
- #if !defined(MS_WINDOWS) && !defined(PYOS_OS2)
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-gethostbyaddr.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-gethostbyaddr.patch
deleted file mode 100644
index 7b04250..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-gethostbyaddr.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- 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 @@
- 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
-+
- #ifndef linux
- # undef HAVE_GETHOSTBYNAME_R_3_ARG
- # undef HAVE_GETHOSTBYNAME_R_5_ARG
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-locale.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-locale.patch
deleted file mode 100644
index bd4fcbf..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-locale.patch
+++ /dev/null
@@ -1,91 +0,0 @@
---- Python-2.7.2.orig/Modules/pwdmodule.c 2010-08-16 22:30:26.000000000 +0200
-+++ Python-2.7.2/Modules/pwdmodule.c 2011-04-20 17:52:12.000000000 +0200
-@@ -75,11 +75,7 @@
- #endif
- SETI(setIndex++, p->pw_uid);
- SETI(setIndex++, p->pw_gid);
--#ifdef __VMS
- SETS(setIndex++, "");
--#else
-- SETS(setIndex++, p->pw_gecos);
--#endif
- SETS(setIndex++, p->pw_dir);
- SETS(setIndex++, p->pw_shell);
-
---- Python-2.7.2.orig/Modules/posixmodule.c 2010-11-26 18:35:50.000000000 +0100
-+++ Python-2.7.2/Modules/posixmodule.c 2011-04-20 17:52:12.000000000 +0200
-@@ -3775,13 +3775,6 @@
- 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)
-- ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */
-- ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm */
--#ifndef __hpux
-- ioctl(slave_fd, I_PUSH, "ttcompat"); /* push ttcompat */
--#endif /* __hpux */
--#endif /* HAVE_CYGWIN */
- #endif /* HAVE_OPENPTY */
-
- return Py_BuildValue("(ii)", master_fd, slave_fd);
---- Python-2.7.2.orig/Objects/stringlib/formatter.h 2010-08-01 12:45:15.000000000 +0200
-+++ Python-2.7.2/Objects/stringlib/formatter.h 2011-04-20 17:52:12.000000000 +0200
-@@ -639,13 +639,7 @@
- get_locale_info(int type, LocaleInfo *locale_info)
- {
- switch (type) {
-- case LT_CURRENT_LOCALE: {
-- 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;
-- }
-+ case LT_CURRENT_LOCALE:
- case LT_DEFAULT_LOCALE:
- locale_info->decimal_point = ".";
- locale_info->thousands_sep = ",";
---- Python-2.7.2.orig/Objects/stringlib/localeutil.h 2009-04-22 15:29:05.000000000 +0200
-+++ Python-2.7.2/Objects/stringlib/localeutil.h 2011-04-20 17:52:12.000000000 +0200
-@@ -202,9 +202,8 @@
- Py_ssize_t n_digits,
- Py_ssize_t min_width)
- {
-- struct lconv *locale_data = localeconv();
-- const char *grouping = locale_data->grouping;
-- const char *thousands_sep = locale_data->thousands_sep;
-+ const char *grouping = "\3\0";
-+ const char *thousands_sep = ",";
-
- return _Py_InsertThousandsGrouping(buffer, n_buffer, digits, n_digits,
- min_width, grouping, thousands_sep);
---- Python-2.7.2.orig/Python/pystrtod.c 2010-05-09 16:46:46.000000000 +0200
-+++ Python-2.7.2/Python/pystrtod.c 2011-04-20 17:52:12.000000000 +0200
-@@ -126,7 +126,6 @@
- {
- char *fail_pos;
- double val = -1.0;
-- struct lconv *locale_data;
- const char *decimal_point;
- size_t decimal_point_len;
- const char *p, *decimal_point_pos;
-@@ -138,8 +137,7 @@
-
- fail_pos = NULL;
-
-- locale_data = localeconv();
-- decimal_point = locale_data->decimal_point;
-+ decimal_point = ".";
- decimal_point_len = strlen(decimal_point);
-
- assert(decimal_point_len != 0);
-@@ -375,8 +373,7 @@
- Py_LOCAL_INLINE(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);
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-remove-corefoundation.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-remove-corefoundation.patch
deleted file mode 100644
index 02a472e..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-remove-corefoundation.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- Python-2.7.2/configure.orig 2012-07-05 16:44:36.000000000 +0200
-+++ Python-2.7.2/configure 2012-07-05 16:44:44.000000000 +0200
-@@ -13732,10 +13732,6 @@
-
- fi
-
--if test $ac_sys_system = Darwin
--then
-- LIBS="$LIBS -framework CoreFoundation"
--fi
-
-
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for %zd printf() format support" >&5
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-setup-flags.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-setup-flags.patch
deleted file mode 100644
index b1f640c..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-setup-flags.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- Python-2.7.2/setup.py.orig 2012-01-08 15:10:39.867332119 +0100
-+++ Python-2.7.2/setup.py 2012-01-08 15:10:45.723331911 +0100
-@@ -445,6 +445,13 @@
- '/lib', '/usr/lib',
- ]
- inc_dirs += ['/usr/include']
-+ else:
-+ cflags = os.environ.get('CFLAGS')
-+ if cflags:
-+ inc_dirs += [x[2:] for x in cflags.split() if x.startswith('-I')]
-+ ldflags = os.environ.get('LDFLAGS')
-+ if ldflags:
-+ lib_dirs += [x[2:] for x in ldflags.split() if x.startswith('-L')]
- exts = []
- missing = []
-
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-termios.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/fix-termios.patch
deleted file mode 100644
index a114e27..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/fix-termios.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- Python-2.7.2.orig/Modules/termios.c 2012-06-12 02:49:39.780162534 +0200
-+++ Python-2.7.2/Modules/termios.c 2012-06-12 02:51:52.092157828 +0200
-@@ -227,6 +227,7 @@
- return Py_None;
- }
-
-+#if 0 // No tcdrain defined for Android.
- PyDoc_STRVAR(termios_tcdrain__doc__,
- "tcdrain(fd) -> None\n\
- \n\
-@@ -246,6 +247,7 @@
- Py_INCREF(Py_None);
- return Py_None;
- }
-+#endif
-
- PyDoc_STRVAR(termios_tcflush__doc__,
- "tcflush(fd, queue) -> None\n\
-@@ -301,8 +303,10 @@
- METH_VARARGS, termios_tcsetattr__doc__},
- {"tcsendbreak", termios_tcsendbreak,
- METH_VARARGS, termios_tcsendbreak__doc__},
-+#if 0 // No tcdrain defined for Android.
- {"tcdrain", termios_tcdrain,
- METH_VARARGS, termios_tcdrain__doc__},
-+#endif
- {"tcflush", termios_tcflush,
- METH_VARARGS, termios_tcflush__doc__},
- {"tcflow", termios_tcflow,
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/parsetuple.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/parsetuple.patch
deleted file mode 100644
index 150c75e..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/parsetuple.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2/configure.orig 2015-06-24 17:47:39.181473779 +0200
-+++ Python-2.7.2/configure 2015-06-24 17:48:31.646173137 +0200
-@@ -5731,7 +5731,7 @@
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether gcc supports ParseTuple __format__" >&5
- $as_echo_n "checking whether gcc supports ParseTuple __format__... " >&6; }
- save_CFLAGS=$CFLAGS
-- CFLAGS="$CFLAGS -Werror"
-+ CFLAGS="$CFLAGS -Werror -Wformat"
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-
diff --git a/p4a/pythonforandroid/recipes/python2legacy/patches/verbose-compilation.patch b/p4a/pythonforandroid/recipes/python2legacy/patches/verbose-compilation.patch
deleted file mode 100644
index 00c89f9..0000000
--- a/p4a/pythonforandroid/recipes/python2legacy/patches/verbose-compilation.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- Python-2.7.2/Makefile.pre.in.orig 2012-01-07 18:25:42.097075564 +0100
-+++ Python-2.7.2/Makefile.pre.in 2012-01-07 18:26:03.289074810 +0100
-@@ -410,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build -v;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build -v;; \
- esac
-
- # Build static library
diff --git a/p4a/pythonforandroid/recipes/python3/__init__.py b/p4a/pythonforandroid/recipes/python3/__init__.py
index c6d5ba5..5894b3f 100644
--- a/p4a/pythonforandroid/recipes/python3/__init__.py
+++ b/p4a/pythonforandroid/recipes/python3/__init__.py
@@ -1,54 +1,429 @@
+import glob
import sh
-from pythonforandroid.python import GuestPythonRecipe
-from pythonforandroid.recipe import Recipe
+import subprocess
+
+from multiprocessing import cpu_count
+from os import environ, utime
+from os.path import dirname, exists, join
+from pathlib import Path
+import shutil
+
+from pythonforandroid.logger import info, warning, shprint
+from pythonforandroid.patching import version_starts_with
+from pythonforandroid.recipe import Recipe, TargetPythonRecipe
+from pythonforandroid.util import (
+ current_directory,
+ ensure_dir,
+ walk_valid_filens,
+ BuildInterruptingException,
+)
+
+NDK_API_LOWER_THAN_SUPPORTED_MESSAGE = (
+ 'Target ndk-api is {ndk_api}, '
+ 'but the python3 recipe supports only {min_ndk_api}+'
+)
-class Python3Recipe(GuestPythonRecipe):
+class Python3Recipe(TargetPythonRecipe):
'''
- The python3's recipe.
+ The python3's recipe
+ ^^^^^^^^^^^^^^^^^^^^
- .. note:: This recipe can be built only against API 21+. Also, in order to
- build certain python modules, we need to add some extra recipes to our
- build requirements:
+ The python 3 recipe can be built with some extra python modules, but to do
+ so, we need some libraries. By default, we ship the python3 recipe with
+ some common libraries, defined in ``depends``. We also support some optional
+ libraries, which are less common that the ones defined in ``depends``, so
+ we added them as optional dependencies (``opt_depends``).
- - ctypes: you must add the recipe for ``libffi``.
+ Below you have a relationship between the python modules and the recipe
+ libraries::
+
+ - _ctypes: you must add the recipe for ``libffi``.
+ - _sqlite3: you must add the recipe for ``sqlite3``.
+ - _ssl: you must add the recipe for ``openssl``.
+ - _bz2: you must add the recipe for ``libbz2`` (optional).
+ - _lzma: you must add the recipe for ``liblzma`` (optional).
+
+ .. note:: This recipe can be built only against API 21+.
+
+ .. versionchanged:: 2019.10.06.post0
+ - Refactored from deleted class ``python.GuestPythonRecipe`` into here
+ - Added optional dependencies: :mod:`~pythonforandroid.recipes.libbz2`
+ and :mod:`~pythonforandroid.recipes.liblzma`
.. versionchanged:: 0.6.0
Refactored into class
:class:`~pythonforandroid.python.GuestPythonRecipe`
'''
- version = '3.7.1'
+ version = '3.9.9'
url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz'
name = 'python3'
- patches = ["patches/fix-ctypes-util-find-library.patch"]
+ patches = [
+ 'patches/pyconfig_detection.patch',
+ 'patches/reproducible-buildinfo.diff',
- if sh.which('lld') is not None:
- patches = patches + ["patches/remove-fix-cortex-a8.patch"]
+ # Python 3.7.1
+ ('patches/py3.7.1_fix-ctypes-util-find-library.patch', version_starts_with("3.7")),
+ ('patches/py3.7.1_fix-zlib-version.patch', version_starts_with("3.7")),
+
+ # Python 3.8.1 & 3.9.X
+ ('patches/py3.8.1.patch', version_starts_with("3.8")),
+ ('patches/py3.8.1.patch', version_starts_with("3.9"))
+ ]
+
+ if shutil.which('lld') is not None:
+ patches = patches + [
+ ("patches/py3.7.1_fix_cortex_a8.patch", version_starts_with("3.7")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.8")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.9"))
+ ]
depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']
- conflicts = ['python3crystax', 'python2', 'python2legacy']
+ # those optional depends allow us to build python compression modules:
+ # - _bz2.so
+ # - _lzma.so
+ opt_depends = ['libbz2', 'liblzma']
+ '''The optional libraries which we would like to get our python linked'''
configure_args = (
'--host={android_host}',
'--build={android_build}',
'--enable-shared',
- '--disable-ipv6',
+ '--enable-ipv6',
'ac_cv_file__dev_ptmx=yes',
'ac_cv_file__dev_ptc=no',
'--without-ensurepip',
'ac_cv_little_endian_double=yes',
'--prefix={prefix}',
- '--exec-prefix={exec_prefix}')
+ '--exec-prefix={exec_prefix}',
+ '--enable-loadable-sqlite-extensions')
+ '''The configure arguments needed to build the python recipe. Those are
+ used in method :meth:`build_arch` (if not overwritten like python3's
+ recipe does).
+ '''
+
+ 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.
+ '''
+
+ 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.'''
+
+ 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().__init__(*args, **kwargs)
+
+ @property
+ def _libpython(self):
+ '''return the python's library name (with extension)'''
+ return 'libpython{link_version}.so'.format(
+ link_version=self.link_version
+ )
+
+ @property
+ def link_version(self):
+ '''return the python's library link version e.g. 3.7m, 3.8'''
+ major, minor = self.major_minor_version_string.split('.')
+ flags = ''
+ if major == '3' and int(minor) < 8:
+ flags += 'm'
+ return '{major}.{minor}{flags}'.format(
+ major=major,
+ minor=minor,
+ flags=flags
+ )
+
+ 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 should_build(self, arch):
+ return not Path(self.link_root(arch.arch), self._libpython).is_file()
+
+ def prebuild_arch(self, arch):
+ super().prebuild_arch(arch)
+ self.ctx.python_recipe = self
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch)
+ env['HOSTARCH'] = arch.command_prefix
+
+ env['CC'] = arch.get_clang_exe(with_target=True)
+
+ 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'])
+
+ env['CFLAGS'] = ' '.join(
+ [
+ '-fPIC',
+ '-DANDROID'
+ ]
+ )
+
+ env['LDFLAGS'] = env.get('LDFLAGS', '')
+ if shutil.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:
+ warning('lld not found, linking without it. '
+ 'Consider installing lld if linker errors occur.')
+
+ return env
def set_libs_flags(self, env, arch):
- env = super(Python3Recipe, self).set_libs_flags(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)
self.configure_args += \
('--with-openssl=' + recipe.get_build_dir(arch.arch),)
+ add_flags(recipe.include_flags(arch),
+ recipe.link_dirs_flags(arch), recipe.link_libs_flags())
+
+ for library_name in {'libbz2', 'liblzma'}:
+ if library_name in self.ctx.recipe_build_order:
+ info(f'Activating flags for {library_name}')
+ recipe = Recipe.get_recipe(library_name, self.ctx)
+ add_flags(recipe.get_library_includes(arch),
+ recipe.get_library_ldflags(arch),
+ recipe.get_library_libs_flag())
+
+ # python build system contains hardcoded zlib version which prevents
+ # the build of zlib module, here we search for android's zlib version
+ # and sets the right flags, so python can be build with android's zlib
+ info("Activating flags for android's zlib")
+ zlib_lib_path = arch.ndk_lib_dir_versioned
+ zlib_includes = self.ctx.ndk.sysroot_include_dir
+ zlib_h = join(zlib_includes, 'zlib.h')
+ try:
+ with open(zlib_h) as fileh:
+ zlib_data = fileh.read()
+ except IOError:
+ raise BuildInterruptingException(
+ "Could not determine android's zlib version, no zlib.h ({}) in"
+ " the NDK dir includes".format(zlib_h)
+ )
+ for line in zlib_data.split('\n'):
+ if line.startswith('#define ZLIB_VERSION '):
+ break
+ else:
+ raise BuildInterruptingException(
+ 'Could not parse zlib.h...so we cannot find zlib version,'
+ 'required by python build,'
+ )
+ env['ZLIB_VERSION'] = line.replace('#define ZLIB_VERSION ', '')
+ add_flags(' -I' + zlib_includes, ' -L' + zlib_lib_path, ' -lz')
+
return env
+ def build_arch(self, arch):
+ if self.ctx.ndk_api < self.MIN_NDK_API:
+ raise BuildInterruptingException(
+ NDK_API_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ ndk_api=self.ctx.ndk_api, min_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'
+
+ 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')
+
+ with current_directory(build_dir):
+ 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)
+
+ shprint(
+ sh.make, 'all', '-j', str(cpu_count()),
+ 'INSTSONAME={lib_name}'.format(lib_name=self._libpython),
+ _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 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]
+ args += ['-OO', '-m', 'compileall', '-b', '-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 the python modules
+ self.compile_python_files(modules_build_dir)
+ # Compile to *.pyc the standard python library
+ self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib'))
+ # Compile to *.pyc the other python packages (site-packages)
+ self.compile_python_files(self.ctx.get_python_install_dir(arch.arch))
+
+ # 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))
+ shutil.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))
+ if 'SOURCE_DATE_EPOCH' in environ:
+ # for reproducible builds
+ stdlib_filens.sort()
+ timestamp = int(environ['SOURCE_DATE_EPOCH'])
+ for filen in stdlib_filens:
+ utime(filen, (timestamp, timestamp))
+ info("Zip {} files into the bundle".format(len(stdlib_filens)))
+ shprint(sh.zip, '-X', stdlib_zip, *stdlib_filens)
+
+ # copy the site-packages into place
+ ensure_dir(join(dirn, 'site-packages'))
+ ensure_dir(self.ctx.get_python_install_dir(arch.arch))
+ # TODO: Improve the API around walking and copying the files
+ with current_directory(self.ctx.get_python_install_dir(arch.arch)):
+ 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)))
+ shutil.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.link_version
+ shprint(
+ sh.cp,
+ join(python_build_dir, python_lib_name + '.so'),
+ join(self.ctx.bootstrap.dist_dir, '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')
+
recipe = Python3Recipe()
diff --git a/p4a/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch b/p4a/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch
deleted file mode 100644
index ac75c83..0000000
--- a/p4a/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
---- a/Lib/ctypes/util.py
-+++ b/Lib/ctypes/util.py
-@@ -67,4 +67,19 @@
- return fname
- return None
-
-+# This patch overrides the find_library to look in the right places on
-+# Android
-+if True:
-+ def find_library(name):
-+ # Check the user app libs and system libraries directory:
-+ app_root = os.path.normpath(os.path.abspath('../../'))
-+ lib_search_dirs = [os.path.join(app_root, 'lib'), "/system/lib"]
-+ for lib_dir in lib_search_dirs:
-+ for filename in os.listdir(lib_dir):
-+ if filename.endswith('.so') and (
-+ filename.startswith("lib" + name + ".") or
-+ filename.startswith(name + ".")):
-+ return os.path.join(lib_dir, filename)
-+ return None
-+
- elif os.name == "posix" and sys.platform == "darwin":
diff --git a/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch b/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch
new file mode 100644
index 0000000..494270d
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch
@@ -0,0 +1,15 @@
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+--- a/Lib/ctypes/util.py
++++ b/Lib/ctypes/util.py
+@@ -67,4 +67,11 @@
+ return fname
+ return None
+
++# This patch overrides the find_library to look in the right places on
++# Android
++if True:
++ from android._ctypes_library_finder import find_library as _find_lib
++ def find_library(name):
++ return _find_lib(name)
++
+ elif os.name == "posix" and sys.platform == "darwin":
diff --git a/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch b/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch
new file mode 100644
index 0000000..0dbffae
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch
@@ -0,0 +1,12 @@
+--- Python-3.7.1/setup.py.orig 2018-10-20 08:04:19.000000000 +0200
++++ Python-3.7.1/setup.py 2019-02-17 00:24:30.715904412 +0100
+@@ -1410,7 +1410,8 @@ class PyBuildExt(build_ext):
+ if zlib_inc is not None:
+ zlib_h = zlib_inc[0] + '/zlib.h'
+ version = '"0.0.0"'
+- version_req = '"1.1.3"'
++ version_req = '"{}"'.format(
++ os.environ.get('ZLIB_VERSION', '1.1.3'))
+ if host_platform == 'darwin' and is_macosx_sdk_path(zlib_h):
+ zlib_h = os.path.join(macosx_sdk_root(), zlib_h[1:])
+ with open(zlib_h) as fp:
diff --git a/p4a/pythonforandroid/recipes/python3/patches/remove-fix-cortex-a8.patch b/p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch
similarity index 100%
rename from p4a/pythonforandroid/recipes/python3/patches/remove-fix-cortex-a8.patch
rename to p4a/pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch
diff --git a/recipes/python3crystax/patch/py3.8.1.patch b/p4a/pythonforandroid/recipes/python3/patches/py3.8.1.patch
similarity index 100%
rename from recipes/python3crystax/patch/py3.8.1.patch
rename to p4a/pythonforandroid/recipes/python3/patches/py3.8.1.patch
diff --git a/recipes/python3crystax/patch/py3.8.1_fix_cortex_a8.patch b/p4a/pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch
similarity index 100%
rename from recipes/python3crystax/patch/py3.8.1_fix_cortex_a8.patch
rename to p4a/pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch
diff --git a/recipes/python3crystax/patch/pyconfig_detection.patch b/p4a/pythonforandroid/recipes/python3/patches/pyconfig_detection.patch
similarity index 100%
rename from recipes/python3crystax/patch/pyconfig_detection.patch
rename to p4a/pythonforandroid/recipes/python3/patches/pyconfig_detection.patch
diff --git a/recipes/python3crystax/patch/reproducible-buildinfo.diff b/p4a/pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff
similarity index 100%
rename from recipes/python3crystax/patch/reproducible-buildinfo.diff
rename to p4a/pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff
diff --git a/p4a/pythonforandroid/recipes/python3crystax/__init__.py b/p4a/pythonforandroid/recipes/python3crystax/__init__.py
deleted file mode 100644
index 0196c92..0000000
--- a/p4a/pythonforandroid/recipes/python3crystax/__init__.py
+++ /dev/null
@@ -1,102 +0,0 @@
-
-from pythonforandroid.recipe import TargetPythonRecipe
-from pythonforandroid.toolchain import shprint
-from pythonforandroid.logger import info, error
-from pythonforandroid.util import ensure_dir, temp_directory
-from os.path import exists, join
-import sh
-
-prebuilt_download_locations = {
- '3.6': ('https://github.com/inclement/crystax_python_builds/'
- 'releases/download/0.1/crystax_python_3.6_armeabi_armeabi-v7a.tar.gz')}
-
-
-class Python3CrystaXRecipe(TargetPythonRecipe):
- version = '3.6'
- url = ''
- name = 'python3crystax'
-
- depends = ['hostpython3crystax']
- conflicts = ['python3', 'python2', 'python2legacy']
-
- from_crystax = True
-
- def get_dir_name(self):
- name = super(Python3CrystaXRecipe, self).get_dir_name()
- name += '-version{}'.format(self.version)
- return name
-
- def build_arch(self, arch):
- # We don't have to actually build anything as CrystaX comes
- # with the necessary modules. They are included by modifying
- # the Android.mk in the jni folder.
-
- # If the Python version to be used is not prebuilt with the CrystaX
- # NDK, we do have to download it.
-
- crystax_python_dir = join(self.ctx.ndk_dir, 'sources', 'python')
- if not exists(join(crystax_python_dir, self.version)):
- info(('The NDK does not have a prebuilt Python {}, trying '
- 'to obtain one.').format(self.version))
-
- if self.version not in prebuilt_download_locations:
- error(('No prebuilt version for Python {} could be found, '
- 'the built cannot continue.'))
- exit(1)
-
- with temp_directory() as td:
- self.download_file(prebuilt_download_locations[self.version],
- join(td, 'downloaded_python'))
- shprint(sh.tar, 'xf', join(td, 'downloaded_python'),
- '--directory', crystax_python_dir)
-
- if not exists(join(crystax_python_dir, self.version)):
- error(('Something went wrong, the directory at {} should '
- 'have been created but does not exist.').format(
- join(crystax_python_dir, self.version)))
-
- if not exists(join(
- crystax_python_dir, self.version, 'libs', arch.arch)):
- error(('The prebuilt Python for version {} does not contain '
- 'binaries for your chosen architecture "{}".').format(
- self.version, arch.arch))
- exit(1)
-
- # TODO: We should have an option to build a new Python. This
- # would also allow linking to openssl and sqlite from CrystaX.
-
- dirn = self.ctx.get_python_install_dir()
- ensure_dir(dirn)
-
- # Instead of using a locally built hostpython, we use the
- # user's Python for now. They must have the right version
- # available. Using e.g. pyenv makes this easy.
- self.ctx.hostpython = 'python{}'.format(self.version)
-
- def create_python_bundle(self, dirn, arch):
- ndk_dir = self.ctx.ndk_dir
- py_recipe = self.ctx.python_recipe
- python_dir = join(ndk_dir, 'sources', 'python',
- py_recipe.version, 'libs', arch.arch)
- shprint(sh.cp, '-r', join(python_dir,
- 'stdlib.zip'), dirn)
- shprint(sh.cp, '-r', join(python_dir,
- 'modules'), dirn)
- shprint(sh.cp, '-r', self.ctx.get_python_install_dir(),
- join(dirn, 'site-packages'))
-
- info('Renaming .so files to reflect cross-compile')
- self.reduce_object_file_names(join(dirn, "site-packages"))
-
- return join(dirn, 'site-packages')
-
- def include_root(self, arch_name):
- return join(self.ctx.ndk_dir, 'sources', 'python', self.major_minor_version_string,
- 'include', 'python')
-
- def link_root(self, arch_name):
- return join(self.ctx.ndk_dir, 'sources', 'python', self.major_minor_version_string,
- 'libs', arch_name)
-
-
-recipe = Python3CrystaXRecipe()
diff --git a/p4a/pythonforandroid/recipes/pytz/__init__.py b/p4a/pythonforandroid/recipes/pytz/__init__.py
index 12133bc..ff9bc37 100644
--- a/p4a/pythonforandroid/recipes/pytz/__init__.py
+++ b/p4a/pythonforandroid/recipes/pytz/__init__.py
@@ -3,8 +3,8 @@ from pythonforandroid.recipe import PythonRecipe
class PytzRecipe(PythonRecipe):
name = 'pytz'
- version = '2015.7'
- url = 'https://pypi.python.org/packages/source/p/pytz/pytz-{version}.tar.bz2'
+ version = '2019.3'
+ url = 'https://pypi.python.org/packages/source/p/pytz/pytz-{version}.tar.gz'
depends = []
diff --git a/p4a/pythonforandroid/recipes/pyyaml/__init__.py b/p4a/pythonforandroid/recipes/pyyaml/__init__.py
deleted file mode 100644
index fcd15d3..0000000
--- a/p4a/pythonforandroid/recipes/pyyaml/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class PyYamlRecipe(PythonRecipe):
- version = "3.12"
- url = 'http://pyyaml.org/download/pyyaml/PyYAML-{version}.tar.gz'
- depends = ["setuptools"]
- site_packages_name = 'pyyaml'
-
-
-recipe = PyYamlRecipe()
diff --git a/p4a/pythonforandroid/recipes/pyzbar/__init__.py b/p4a/pythonforandroid/recipes/pyzbar/__init__.py
index ccfcd9b..cf78a55 100644
--- a/p4a/pythonforandroid/recipes/pyzbar/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyzbar/__init__.py
@@ -13,10 +13,10 @@ class PyZBarRecipe(PythonRecipe):
depends = ['setuptools', 'libzbar']
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(PyZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
libzbar = self.get_recipe('libzbar', self.ctx)
libzbar_dir = libzbar.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
diff --git a/p4a/pythonforandroid/recipes/pyzmq/__init__.py b/p4a/pythonforandroid/recipes/pyzmq/__init__.py
index 5f9614d..41addc8 100644
--- a/p4a/pythonforandroid/recipes/pyzmq/__init__.py
+++ b/p4a/pythonforandroid/recipes/pyzmq/__init__.py
@@ -10,16 +10,16 @@ import glob
class PyZMQRecipe(CythonRecipe):
name = 'pyzmq'
- version = 'master'
- url = 'https://github.com/zeromq/pyzmq/archive/{version}.zip'
+ version = '20.0.0'
+ url = 'https://github.com/zeromq/pyzmq/archive/v{version}.zip'
site_packages_name = 'zmq'
- depends = ['libzmq']
+ depends = ['setuptools', 'libzmq']
cython_args = ['-Izmq/utils',
'-Izmq/backend/cython',
'-Izmq/devices']
def get_recipe_env(self, arch=None):
- env = super(PyZMQRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# TODO: fix hardcoded path
# This is required to prevent issue with _io.so import.
# hostpython = self.get_recipe('hostpython2', self.ctx)
@@ -43,9 +43,9 @@ class PyZMQRecipe(CythonRecipe):
[global]
zmq_prefix = {}
skip_check_zmq = True
-""".format(libzmq_prefix))
+""".format(libzmq_prefix).encode())
- return super(PyZMQRecipe, self).build_cython_components(arch)
+ return super().build_cython_components(arch)
with current_directory(self.get_build_dir(arch.arch)):
hostpython = sh.Command(self.hostpython_location)
diff --git a/p4a/pythonforandroid/recipes/regex/__init__.py b/p4a/pythonforandroid/recipes/regex/__init__.py
index 9533905..6ac9148 100644
--- a/p4a/pythonforandroid/recipes/regex/__init__.py
+++ b/p4a/pythonforandroid/recipes/regex/__init__.py
@@ -3,10 +3,11 @@ from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class RegexRecipe(CompiledComponentsPythonRecipe):
name = 'regex'
- version = '2017.07.28'
- url = 'https://pypi.python.org/packages/d1/23/5fa829706ee1d4452552eb32e0bfc1039553e01f50a8754c6f7152e85c1b/regex-{version}.tar.gz'
+ version = '2019.06.08'
+ url = 'https://pypi.python.org/packages/source/r/regex/regex-{version}.tar.gz' # noqa
depends = ['setuptools']
+ call_hostpython_via_targetpython = False
recipe = RegexRecipe()
diff --git a/p4a/pythonforandroid/recipes/reportlab/__init__.py b/p4a/pythonforandroid/recipes/reportlab/__init__.py
index d5e8001..60f1a07 100644
--- a/p4a/pythonforandroid/recipes/reportlab/__init__.py
+++ b/p4a/pythonforandroid/recipes/reportlab/__init__.py
@@ -6,14 +6,14 @@ from pythonforandroid.logger import (info, shprint)
class ReportLabRecipe(CompiledComponentsPythonRecipe):
- version = 'c088826211ca'
- url = 'https://bitbucket.org/rptlab/reportlab/get/{version}.tar.gz'
+ version = 'fe660f227cac'
+ url = 'https://hg.reportlab.com/hg-public/reportlab/archive/{version}.tar.gz'
depends = ['freetype']
call_hostpython_via_targetpython = False
def prebuild_arch(self, arch):
if not self.is_patched(arch):
- super(ReportLabRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
recipe_dir = self.get_build_dir(arch.arch)
# Some versions of reportlab ship with a GPL-licensed font.
@@ -22,9 +22,9 @@ class ReportLabRecipe(CompiledComponentsPythonRecipe):
font_dir = os.path.join(recipe_dir,
"src", "reportlab", "fonts")
if os.path.exists(font_dir):
- for l in os.listdir(font_dir):
- if l.lower().startswith('darkgarden'):
- os.remove(os.path.join(font_dir, l))
+ for file in os.listdir(font_dir):
+ if file.lower().startswith('darkgarden'):
+ os.remove(os.path.join(font_dir, file))
# Apply patches:
self.apply_patch('patches/fix-setup.patch', arch.arch)
diff --git a/p4a/pythonforandroid/recipes/requests/__init__.py b/p4a/pythonforandroid/recipes/requests/__init__.py
deleted file mode 100644
index 22a6268..0000000
--- a/p4a/pythonforandroid/recipes/requests/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class RequestsRecipe(PythonRecipe):
- version = '2.13.0'
- url = 'https://github.com/kennethreitz/requests/archive/v{version}.tar.gz'
- depends = ['setuptools']
- site_packages_name = 'requests'
- call_hostpython_via_targetpython = False
-
-
-recipe = RequestsRecipe()
diff --git a/p4a/pythonforandroid/recipes/scipy/__init__.py b/p4a/pythonforandroid/recipes/scipy/__init__.py
new file mode 100644
index 0000000..455a988
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/scipy/__init__.py
@@ -0,0 +1,81 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+from multiprocessing import cpu_count
+from os.path import join
+from os import environ
+from pythonforandroid.util import build_platform
+
+
+def arch_to_toolchain(arch):
+ if 'arm' in arch.arch:
+ return arch.command_prefix
+ return arch.arch
+
+
+class ScipyRecipe(CompiledComponentsPythonRecipe):
+
+ version = '1.8.1'
+ url = f'https://github.com/scipy/scipy/releases/download/v{version}/scipy-{version}.zip'
+ site_packages_name = 'scipy'
+ depends = ['setuptools', 'cython', 'numpy', 'lapack', 'pybind11']
+ call_hostpython_via_targetpython = False
+ need_stl_shared = True
+
+ def build_compiled_components(self, arch):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super().build_compiled_components(arch)
+ self.setup_extra_args = []
+
+ def rebuild_compiled_components(self, arch, env):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super().rebuild_compiled_components(arch, env)
+ self.setup_extra_args = []
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ arch_env = arch.get_env()
+
+ env['LDFLAGS'] = arch_env['LDFLAGS']
+ env['LDFLAGS'] += ' -L{} -lpython{}'.format(
+ self.ctx.python_recipe.link_root(arch.arch),
+ self.ctx.python_recipe.link_version,
+ )
+
+ ndk_dir = environ["LEGACY_NDK"]
+ GCC_VER = '4.9'
+ HOST = build_platform
+ suffix = '64' if '64' in arch.arch else ''
+
+ prefix = arch.command_prefix
+ CLANG_BIN = f'{ndk_dir}/toolchains/llvm/prebuilt/{HOST}/bin/'
+ GCC = f'{ndk_dir}/toolchains/{arch_to_toolchain(arch)}-{GCC_VER}/prebuilt/{HOST}'
+ libgfortran = f'{GCC}/{prefix}/lib{suffix}'
+ numpylib = self.ctx.get_python_install_dir(arch.arch) + '/numpy'
+ arch_cflags = ' '.join(arch.arch_cflags)
+ LDSHARED_opts = f'-target {arch.target} {arch_cflags} ' + ' '.join(arch.common_ldshared)
+
+ # TODO: add pythran support
+ env['SCIPY_USE_PYTHRAN'] = '0'
+
+ lapack_dir = join(Recipe.get_recipe('lapack', self.ctx).get_build_dir(arch.arch), 'build', 'install')
+ env['LAPACK'] = f'{lapack_dir}/lib'
+ env['BLAS'] = env['LAPACK']
+
+ # compilers
+ env['F77'] = f'{GCC}/bin/{prefix}-gfortran'
+ env['F90'] = f'{GCC}/bin/{prefix}-gfortran'
+ env['CC'] = f'{CLANG_BIN}clang -target {arch.target} {arch_cflags}'
+ env['CXX'] = f'{CLANG_BIN}clang++ -target {arch.target} {arch_cflags}'
+
+ # scipy expects ldshared to be a single executable without options
+ env['LDSHARED'] = f'{CLANG_BIN}/clang'
+
+ # erase the default NDK C++ include options
+ env['CPPFLAGS'] = '-DANDROID'
+
+ # configure linker
+ env['LDFLAGS'] += f' {LDSHARED_opts} -L{libgfortran} -L{numpylib}/core/lib -L{numpylib}/random/lib'
+ env['LDFLAGS'] += f' -l{self.stl_lib_name}'
+ return env
+
+
+recipe = ScipyRecipe()
diff --git a/p4a/pythonforandroid/recipes/scrypt/__init__.py b/p4a/pythonforandroid/recipes/scrypt/__init__.py
index 26b8048..7f23539 100644
--- a/p4a/pythonforandroid/recipes/scrypt/__init__.py
+++ b/p4a/pythonforandroid/recipes/scrypt/__init__.py
@@ -13,7 +13,7 @@ class ScryptRecipe(CythonRecipe):
"""
Adds openssl recipe to include and library path.
"""
- env = super(ScryptRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
openssl_recipe = self.get_recipe('openssl', self.ctx)
env['CFLAGS'] += openssl_recipe.include_flags(arch)
env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))
diff --git a/p4a/pythonforandroid/recipes/sdl/__init__.py b/p4a/pythonforandroid/recipes/sdl/__init__.py
deleted file mode 100644
index d0f650a..0000000
--- a/p4a/pythonforandroid/recipes/sdl/__init__.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from pythonforandroid.recipe import BootstrapNDKRecipe
-from pythonforandroid.toolchain import current_directory, info, shprint
-from os.path import exists, join
-import sh
-
-
-class LibSDLRecipe(BootstrapNDKRecipe):
- version = "1.2.14"
- url = None
- name = 'sdl'
- depends = ['python2legacy', 'pygame_bootstrap_components']
- conflicts = ['sdl2']
-
- def build_arch(self, arch):
-
- if exists(join(self.ctx.libs_dir, 'libsdl.so')):
- info('libsdl.so already exists, skipping sdl build.')
- return
-
- env = self.get_recipe_env(arch)
-
- with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, 'V=1', _env=env, _tail=20, _critical=True)
-
- libs_dir = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)
- import os
- contents = list(os.walk(libs_dir))[0][-1]
- for content in contents:
- shprint(sh.cp, '-a', join(self.ctx.bootstrap.build_dir, 'libs', arch.arch, content),
- self.ctx.libs_dir)
-
- def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):
- env = super(LibSDLRecipe, self).get_recipe_env(
- arch=arch, with_flags_in_cc=with_flags_in_cc, with_python=with_python)
- return env
-
-
-recipe = LibSDLRecipe()
diff --git a/p4a/pythonforandroid/recipes/sdl2/__init__.py b/p4a/pythonforandroid/recipes/sdl2/__init__.py
index bbfadc2..78e5e1f 100644
--- a/p4a/pythonforandroid/recipes/sdl2/__init__.py
+++ b/p4a/pythonforandroid/recipes/sdl2/__init__.py
@@ -1,29 +1,42 @@
+from os.path import exists, join
+
from pythonforandroid.recipe import BootstrapNDKRecipe
from pythonforandroid.toolchain import current_directory, shprint
import sh
class LibSDL2Recipe(BootstrapNDKRecipe):
- version = "2.0.9"
- url = "https://www.libsdl.org/release/SDL2-{version}.tar.gz"
- md5sum = 'f2ecfba915c54f7200f504d8b48a5dfe'
+ version = "2.26.0"
+ url = "https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL2-{version}.tar.gz"
+ md5sum = '35bc58cfe41b8fb6c8e6646be26fa47e'
dir_name = 'SDL'
+ patches = ['remove-extra-include.patch']
+
depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf']
- conflicts = ['sdl', 'pygame', 'pygame_bootstrap_components']
def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):
- env = super(LibSDL2Recipe, self).get_recipe_env(
+ env = super().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 should_build(self, arch):
+ libdir = join(self.get_build_dir(arch.arch), "../..", "libs", arch.arch)
+ libs = ['libmain.so', 'libSDL2.so', 'libSDL2_image.so', 'libSDL2_mixer.so', 'libSDL2_ttf.so']
+ return not all(exists(join(libdir, x)) for x in libs)
+
def build_arch(self, arch):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", _env=env)
+ shprint(
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
+ "V=1",
+ "NDK_DEBUG=" + ("1" if self.ctx.build_as_debuggable else "0"),
+ _env=env
+ )
recipe = LibSDL2Recipe()
diff --git a/p4a/pythonforandroid/recipes/sdl2/remove-extra-include.patch b/p4a/pythonforandroid/recipes/sdl2/remove-extra-include.patch
new file mode 100644
index 0000000..f0fda08
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/sdl2/remove-extra-include.patch
@@ -0,0 +1,12 @@
+diff -Naur SDL.orig/Android.mk SDL/Android.mk
+--- SDL.orig/Android.mk 2022-11-22 07:41:32
++++ SDL/Android.mk 2022-11-22 07:42:00
+@@ -12,7 +12,7 @@
+
+ LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+
+-LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)/include
++LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
+
+ LOCAL_SRC_FILES := \
+ $(subst $(LOCAL_PATH)/,, \
diff --git a/p4a/pythonforandroid/recipes/sdl2_image/__init__.py b/p4a/pythonforandroid/recipes/sdl2_image/__init__.py
index 920b3ae..a91c6f1 100644
--- a/p4a/pythonforandroid/recipes/sdl2_image/__init__.py
+++ b/p4a/pythonforandroid/recipes/sdl2_image/__init__.py
@@ -1,14 +1,25 @@
+import os
+import sh
+from pythonforandroid.logger import shprint
from pythonforandroid.recipe import BootstrapNDKRecipe
+from pythonforandroid.util import current_directory
class LibSDL2Image(BootstrapNDKRecipe):
- version = '2.0.4'
- url = 'https://www.libsdl.org/projects/SDL_image/release/SDL2_image-{version}.tar.gz'
+ version = '2.6.2'
+ url = 'https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL2_image-{version}.tar.gz'
dir_name = 'SDL2_image'
- patches = ['toggle_jpg_png_webp.patch',
- 'extra_cflags.patch',
- ]
+ patches = ['enable-webp.patch']
+
+ def prebuild_arch(self, arch):
+ # We do not have a folder for each arch on BootstrapNDKRecipe, so we
+ # need to skip the external deps download if we already have done it.
+ external_deps_dir = os.path.join(self.get_build_dir(arch.arch), "external")
+ if not os.path.exists(os.path.join(external_deps_dir, "libwebp")):
+ with current_directory(external_deps_dir):
+ shprint(sh.Command("./download.sh"))
+ super().prebuild_arch(arch)
recipe = LibSDL2Image()
diff --git a/p4a/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch b/p4a/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch
deleted file mode 100644
index 123e5c5..0000000
--- a/p4a/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/Android.mk b/Android.mk
-index d48111b..3108b2c 100644
---- a/Android.mk
-+++ b/Android.mk
-@@ -22,7 +22,7 @@ SUPPORT_WEBP := false
- WEBP_LIBRARY_PATH := external/libwebp-0.3.0
-
-
--LOCAL_C_INCLUDES := $(LOCAL_PATH)
-+LOCAL_C_INCLUDES := $(LOCAL_PATH) $(NDK_PLATFORM_INCLUDE_DIR)
- LOCAL_CFLAGS := -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM -DLOAD_PCX -DLOAD_PNM \
- -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
- LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays $(EXTRA_CFLAGS)
diff --git a/p4a/pythonforandroid/recipes/sdl2_image/enable-webp.patch b/p4a/pythonforandroid/recipes/sdl2_image/enable-webp.patch
new file mode 100644
index 0000000..98d72f2
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/sdl2_image/enable-webp.patch
@@ -0,0 +1,12 @@
+diff -Naur SDL2_image.orig/Android.mk SDL2_image/Android.mk
+--- SDL2_image.orig/Android.mk 2022-10-03 20:51:52.000000000 +0200
++++ SDL2_image/Android.mk 2022-10-03 20:52:48.000000000 +0200
+@@ -32,7 +32,7 @@
+
+ # Enable this if you want to support loading WebP images
+ # The library path should be a relative path to this directory.
+-SUPPORT_WEBP ?= false
++SUPPORT_WEBP := true
+ WEBP_LIBRARY_PATH := external/libwebp
+
+
diff --git a/p4a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch b/p4a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
deleted file mode 100644
index c2b875b..0000000
--- a/p4a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- SDL2_image-2.0.4/Android.mk.orig 2018-10-31 15:58:52.000000000 +0100
-+++ SDL2_image-2.0.4/Android.mk 2019-02-07 21:57:29.552365123 +0100
-@@ -61,6 +61,8 @@ LOCAL_SRC_FILES := \
-
- LOCAL_CFLAGS := -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM -DLOAD_PCX -DLOAD_PNM \
- -DLOAD_SVG -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
-+LOCAL_CFLAGS += $(EXTRA_CFLAGS)
-+
- LOCAL_LDLIBS :=
- LOCAL_STATIC_LIBRARIES :=
- LOCAL_SHARED_LIBRARIES := SDL2
diff --git a/p4a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch b/p4a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
deleted file mode 100644
index f7dce8a..0000000
--- a/p4a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- SDL2_image-2.0.4/Android.mk.orig 2018-10-31 15:58:52.000000000 +0100
-+++ SDL2_image-2.0.4/Android.mk 2019-02-07 23:51:51.740299680 +0100
-@@ -3,18 +3,18 @@ SDL_IMAGE_LOCAL_PATH := $(call my-dir)
-
- # Enable this if you want to support loading JPEG images
- # The library path should be a relative path to this directory.
--SUPPORT_JPG ?= true
-+SUPPORT_JPG := true
- JPG_LIBRARY_PATH := external/jpeg-9b
-
- # Enable this if you want to support loading PNG images
- # The library path should be a relative path to this directory.
--SUPPORT_PNG ?= true
-+SUPPORT_PNG := true
- PNG_LIBRARY_PATH := external/libpng-1.6.32
-
- # Enable this if you want to support loading WebP images
- # The library path should be a relative path to this directory.
--SUPPORT_WEBP ?= true
--WEBP_LIBRARY_PATH := external/libwebp-0.6.0
-+SUPPORT_WEBP := true
-+WEBP_LIBRARY_PATH := external/libwebp-1.0.0
-
-
- # Build the library
diff --git a/p4a/pythonforandroid/recipes/sdl2_mixer/__init__.py b/p4a/pythonforandroid/recipes/sdl2_mixer/__init__.py
index 1a8e0a9..0f02c4c 100644
--- a/p4a/pythonforandroid/recipes/sdl2_mixer/__init__.py
+++ b/p4a/pythonforandroid/recipes/sdl2_mixer/__init__.py
@@ -1,12 +1,17 @@
+import os
+
from pythonforandroid.recipe import BootstrapNDKRecipe
class LibSDL2Mixer(BootstrapNDKRecipe):
- version = '2.0.1'
- url = 'https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-{version}.tar.gz'
+ version = '2.6.2'
+ url = 'https://github.com/libsdl-org/SDL_mixer/releases/download/release-{version}/SDL2_mixer-{version}.tar.gz'
dir_name = 'SDL2_mixer'
- patches = ['toggle_modplug_mikmod_smpeg_ogg.patch']
+ def get_include_dirs(self, arch):
+ return [
+ os.path.join(self.ctx.bootstrap.build_dir, "jni", "SDL2_mixer", "include")
+ ]
recipe = LibSDL2Mixer()
diff --git a/p4a/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch b/p4a/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch
deleted file mode 100644
index 0d7b554..0000000
--- a/p4a/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- orig/Android.mk 2016-01-03 07:15:57.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:28:55.169697882 +0200
-@@ -6,22 +6,22 @@
-
- # Enable this if you want to support loading MOD music via modplug
- # The library path should be a relative path to this directory.
--SUPPORT_MOD_MODPLUG ?= true
-+SUPPORT_MOD_MODPLUG := false
- MODPLUG_LIBRARY_PATH := external/libmodplug-0.8.8.4
-
- # Enable this if you want to support loading MOD music via mikmod
- # The library path should be a relative path to this directory.
--SUPPORT_MOD_MIKMOD ?= true
-+SUPPORT_MOD_MIKMOD := false
- MIKMOD_LIBRARY_PATH := external/libmikmod-3.1.12
-
- # Enable this if you want to support loading MP3 music via SMPEG
- # The library path should be a relative path to this directory.
--SUPPORT_MP3_SMPEG ?= true
-+SUPPORT_MP3_SMPEG := false
- SMPEG_LIBRARY_PATH := external/smpeg2-2.0.0
-
- # Enable this if you want to support loading OGG Vorbis music via Tremor
- # The library path should be a relative path to this directory.
--SUPPORT_OGG ?= true
-+SUPPORT_OGG := true
- OGG_LIBRARY_PATH := external/libogg-1.3.1
- VORBIS_LIBRARY_PATH := external/libvorbisidec-1.2.1
-
diff --git a/p4a/pythonforandroid/recipes/sdl2_ttf/__init__.py b/p4a/pythonforandroid/recipes/sdl2_ttf/__init__.py
index 2d0a629..4934bd4 100644
--- a/p4a/pythonforandroid/recipes/sdl2_ttf/__init__.py
+++ b/p4a/pythonforandroid/recipes/sdl2_ttf/__init__.py
@@ -2,8 +2,8 @@ from pythonforandroid.recipe import BootstrapNDKRecipe
class LibSDL2TTF(BootstrapNDKRecipe):
- version = '2.0.14'
- url = 'https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-{version}.tar.gz'
+ version = '2.20.1'
+ url = 'https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL2_ttf-{version}.tar.gz'
dir_name = 'SDL2_ttf'
diff --git a/p4a/pythonforandroid/recipes/secp256k1/__init__.py b/p4a/pythonforandroid/recipes/secp256k1/__init__.py
index 8898031..1b30642 100644
--- a/p4a/pythonforandroid/recipes/secp256k1/__init__.py
+++ b/p4a/pythonforandroid/recipes/secp256k1/__init__.py
@@ -10,16 +10,21 @@ class Secp256k1Recipe(CppCompiledComponentsPythonRecipe):
call_hostpython_via_targetpython = False
depends = [
- 'openssl', ('hostpython3', 'hostpython2', 'hostpython3crystax'),
- ('python2', 'python3', 'python3crystax'), 'setuptools',
- 'libffi', 'cffi', 'libsecp256k1']
+ 'openssl',
+ 'hostpython3',
+ 'python3',
+ 'setuptools',
+ 'libffi',
+ 'cffi',
+ 'libsecp256k1'
+ ]
patches = [
"cross_compile.patch", "drop_setup_requires.patch",
"pkg-config.patch", "find_lib.patch", "no-download.patch"]
def get_recipe_env(self, arch=None):
- env = super(Secp256k1Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)
libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)
env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include')
diff --git a/p4a/pythonforandroid/recipes/setuptools/__init__.py b/p4a/pythonforandroid/recipes/setuptools/__init__.py
index 6a4b650..8190f8e 100644
--- a/p4a/pythonforandroid/recipes/setuptools/__init__.py
+++ b/p4a/pythonforandroid/recipes/setuptools/__init__.py
@@ -2,14 +2,10 @@ from pythonforandroid.recipe import PythonRecipe
class SetuptoolsRecipe(PythonRecipe):
- version = '40.0.0'
- url = 'https://pypi.python.org/packages/source/s/setuptools/setuptools-{version}.zip'
+ version = '51.3.3'
+ url = 'https://pypi.python.org/packages/source/s/setuptools/setuptools-{version}.tar.gz'
call_hostpython_via_targetpython = False
install_in_hostpython = True
- depends = [('python2', 'python2legacy', 'python3', 'python3crystax')]
- # this recipe seems to control the dependency graph in some way, because
- # if removed the python2legacy recipe fails to solve the dependency order
- # when using the sdl2 bootstrap...so be careful removing this line!!!
recipe = SetuptoolsRecipe()
diff --git a/p4a/pythonforandroid/recipes/shapely/__init__.py b/p4a/pythonforandroid/recipes/shapely/__init__.py
index e0b0937..fb3da7c 100644
--- a/p4a/pythonforandroid/recipes/shapely/__init__.py
+++ b/p4a/pythonforandroid/recipes/shapely/__init__.py
@@ -1,21 +1,33 @@
-from pythonforandroid.recipe import Recipe, CythonRecipe
+from pythonforandroid.recipe import CythonRecipe
+from os.path import join
class ShapelyRecipe(CythonRecipe):
- version = '1.5'
- url = 'https://github.com/Toblerity/Shapely/archive/master.zip'
+ version = '1.7a1'
+ url = 'https://github.com/Toblerity/Shapely/archive/{version}.tar.gz'
depends = ['setuptools', 'libgeos']
+
call_hostpython_via_targetpython = False
- patches = ['setup.patch'] # Patch to force setup to fail when C extention fails to build
+ # Patch to avoid libgeos check (because it fails), insert environment
+ # variables for our libgeos build (includes, lib paths...) and force
+ # the cython's compilation to raise an error in case that it fails
+ patches = ['setup.patch']
- # setup_extra_args = ['sdist'] # DontForce Cython
+ # Don't Force Cython
+ # setup_extra_args = ['sdist']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch)
+
+ libgeos_install = join(self.get_recipe(
+ 'libgeos', self.ctx).get_build_dir(arch.arch), 'install_target')
+ # All this `GEOS_X` variables should be string types, separated
+ # by commas in case that we need to pass more than one value
+ env['GEOS_INCLUDE_DIRS'] = join(libgeos_install, 'include')
+ env['GEOS_LIBRARY_DIRS'] = join(libgeos_install, 'lib')
+ env['GEOS_LIBRARIES'] = 'geos_c,geos'
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- """ Add libgeos headers to path """
- env = super(ShapelyRecipe, self).get_recipe_env(arch, with_flags_in_cc)
- libgeos_dir = Recipe.get_recipe('libgeos', self.ctx).get_build_dir(arch.arch)
- env['CFLAGS'] += " -I{}/dist/include".format(libgeos_dir)
return env
diff --git a/p4a/pythonforandroid/recipes/shapely/setup.patch b/p4a/pythonforandroid/recipes/shapely/setup.patch
index 9523f35..7fd1ca9 100644
--- a/p4a/pythonforandroid/recipes/shapely/setup.patch
+++ b/p4a/pythonforandroid/recipes/shapely/setup.patch
@@ -1,12 +1,44 @@
-*** shapely/setup.py 2016-06-29 11:29:49.000000000 -0400
---- b/setup.py 2016-07-09 01:51:37.759670990 -0400
-***************
-*** 359,364 ****
---- 359,365 ----
- construct_build_ext(existing_build_ext)
- setup(ext_modules=ext_modules, **setup_args)
- except BuildFailed as ex:
-+ raise # Force python only build to fail
- BUILD_EXT_WARNING = "The C extension could not be compiled, " \
- "speedups are not enabled."
- log.warn(ex)
+This patch does three things:
+ - disable the libgeos check, because, even setting the proper env variables,
+ it fails to load our libgeos library, so we skip that because it's not
+ mandatory for the cythonizing.
+ - sets some environment variables into the setup.py file, so we can pass
+ our libgeos information (includes, lib path and libraries)
+ - force to raise an error when cython file to compile (our current build
+ system relies on this failure to do the proper `cythonizing`, if we don't
+ raise the error, we will end up with the package installed without the
+ speed optimizations.
+--- Shapely-1.7a1/setup.py.orig 2018-07-29 22:53:13.000000000 +0200
++++ Shapely-1.7a1/setup.py 2019-02-24 14:26:19.178610660 +0100
+@@ -82,8 +82,8 @@ if not (py_version == (2, 7) or py_versi
+
+ # Get geos_version from GEOS dynamic library, which depends on
+ # GEOS_LIBRARY_PATH and/or GEOS_CONFIG environment variables
+-from shapely._buildcfg import geos_version_string, geos_version, \
+- geos_config, get_geos_config
++# from shapely._buildcfg import geos_version_string, geos_version, \
++# geos_config, get_geos_config
+
+ logging.basicConfig()
+ log = logging.getLogger(__file__)
+@@ -248,9 +248,9 @@ if sys.platform == 'win32':
+ setup_args['package_data']['shapely'].append('shapely/DLLs/*.dll')
+
+ # Prepare build opts and args for the speedups extension module.
+-include_dirs = []
+-library_dirs = []
+-libraries = []
++include_dirs = os.environ.get('GEOS_INCLUDE_DIRS', '').split(',')
++library_dirs = os.environ.get('GEOS_LIBRARY_DIRS', '').split(',')
++libraries = os.environ.get('GEOS_LIBRARIES', '').split(',')
+ extra_link_args = []
+
+ # If NO_GEOS_CONFIG is set in the environment, geos-config will not
+@@ -375,6 +375,7 @@ try:
+ construct_build_ext(existing_build_ext)
+ setup(ext_modules=ext_modules, **setup_args)
+ except BuildFailed as ex:
++ raise # Force python only build to fail
+ BUILD_EXT_WARNING = "The C extension could not be compiled, " \
+ "speedups are not enabled."
+ log.warn(ex)
diff --git a/p4a/pythonforandroid/recipes/simple-crypt/__init__.py b/p4a/pythonforandroid/recipes/simple-crypt/__init__.py
deleted file mode 100644
index 94c5f51..0000000
--- a/p4a/pythonforandroid/recipes/simple-crypt/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class SimpleCryptRecipe(PythonRecipe):
- version = '4.1.7'
- url = 'https://pypi.python.org/packages/source/s/simple-crypt/simple-crypt-{version}.tar.gz'
- depends = ['pycrypto']
- site_packages_name = 'simplecrypt'
-
-
-recipe = SimpleCryptRecipe()
diff --git a/p4a/pythonforandroid/recipes/six/__init__.py b/p4a/pythonforandroid/recipes/six/__init__.py
index 91adc6c..3be8ce7 100644
--- a/p4a/pythonforandroid/recipes/six/__init__.py
+++ b/p4a/pythonforandroid/recipes/six/__init__.py
@@ -1,14 +1,10 @@
-
from pythonforandroid.recipe import PythonRecipe
class SixRecipe(PythonRecipe):
- version = '1.9.0'
+ version = '1.15.0'
url = 'https://pypi.python.org/packages/source/s/six/six-{version}.tar.gz'
- depends = [('python2', 'python2legacy', 'python3', 'python3crystax')]
- # this recipe seems to control the dependency graph in some way, because
- # if removed the python2legacy recipe fails to solve the dependency order
- # when using the pygame bootstrap...so be careful removing this line!!!
+ depends = ['setuptools']
recipe = SixRecipe()
diff --git a/p4a/pythonforandroid/recipes/snappy/__init__.py b/p4a/pythonforandroid/recipes/snappy/__init__.py
index 4ca61a2..c57f797 100644
--- a/p4a/pythonforandroid/recipes/snappy/__init__.py
+++ b/p4a/pythonforandroid/recipes/snappy/__init__.py
@@ -1,13 +1,28 @@
-from pythonforandroid.toolchain import Recipe
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from os.path import join
+import sh
class SnappyRecipe(Recipe):
- version = '1.1.3'
- url = 'https://github.com/google/snappy/releases/download/{version}/snappy-{version}.tar.gz'
+ version = '1.1.7'
+ url = 'https://github.com/google/snappy/archive/{version}.tar.gz'
+ built_libraries = {'libsnappy.so': '.'}
- def should_build(self, arch):
- # Only download to use in leveldb recipe
- return False
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ source_dir = self.get_build_dir(arch.arch)
+ with current_directory(source_dir):
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DBUILD_SHARED_LIBS=1',
+ _env=env)
+ shprint(sh.make, _env=env)
recipe = SnappyRecipe()
diff --git a/p4a/pythonforandroid/recipes/sqlalchemy/__init__.py b/p4a/pythonforandroid/recipes/sqlalchemy/__init__.py
index 974667a..9837a59 100644
--- a/p4a/pythonforandroid/recipes/sqlalchemy/__init__.py
+++ b/p4a/pythonforandroid/recipes/sqlalchemy/__init__.py
@@ -3,8 +3,9 @@ from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class SQLAlchemyRecipe(CompiledComponentsPythonRecipe):
name = 'sqlalchemy'
- version = '1.0.9'
+ version = '1.3.3'
url = 'https://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-{version}.tar.gz'
+ call_hostpython_via_targetpython = False
depends = ['setuptools']
diff --git a/p4a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch b/p4a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
index 1820d09..46bdf60 100644
--- a/p4a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
+++ b/p4a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
@@ -1,12 +1,10 @@
-diff --git a/setup.py b/setup.py
-index 09b524c..1e65772 100644
---- a/setup.py
-+++ b/setup.py
-@@ -125,6 +125,7 @@ def run_setup(with_cext):
- setup(name="SQLAlchemy",
- version=VERSION,
- description="Database Abstraction Library",
-+ zip_safe=False,
- author="Mike Bayer",
- author_email="mike_mp@zzzcomputing.com",
- url="http://www.sqlalchemy.org",
+--- a/setup.py 2019-04-15 17:45:03.000000000 +0200
++++ b/setup.py 2019-04-16 20:12:19.056710749 +0200
+@@ -145,6 +145,7 @@
+ name="SQLAlchemy",
+ version=VERSION,
+ description="Database Abstraction Library",
++ zip_safe=False,
+ author="Mike Bayer",
+ author_email="mike_mp@zzzcomputing.com",
+ url="http://www.sqlalchemy.org",
diff --git a/p4a/pythonforandroid/recipes/sqlite3/__init__.py b/p4a/pythonforandroid/recipes/sqlite3/__init__.py
index cfdcb0f..955d808 100644
--- a/p4a/pythonforandroid/recipes/sqlite3/__init__.py
+++ b/p4a/pythonforandroid/recipes/sqlite3/__init__.py
@@ -5,29 +5,29 @@ import sh
class Sqlite3Recipe(NDKRecipe):
- version = '3.15.1'
+ version = '3.35.5'
# Don't forget to change the URL when changing the version
- url = 'https://www.sqlite.org/2016/sqlite-amalgamation-3150100.zip'
+ url = 'https://www.sqlite.org/2021/sqlite-amalgamation-3350500.zip'
generated_libraries = ['sqlite3']
def should_build(self, arch):
return not self.has_libs(arch, 'libsqlite3.so')
def prebuild_arch(self, arch):
- super(Sqlite3Recipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
# Copy the Android make file
sh.mkdir('-p', join(self.get_build_dir(arch.arch), 'jni'))
shutil.copyfile(join(self.get_recipe_dir(), 'Android.mk'),
join(self.get_build_dir(arch.arch), 'jni/Android.mk'))
def build_arch(self, arch, *extra_args):
- super(Sqlite3Recipe, self).build_arch(arch)
+ super().build_arch(arch)
# Copy the shared library
shutil.copyfile(join(self.get_build_dir(arch.arch), 'libs', arch.arch, 'libsqlite3.so'),
join(self.ctx.get_libs_dir(arch.arch), 'libsqlite3.so'))
def get_recipe_env(self, arch):
- env = super(Sqlite3Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['NDK_PROJECT_PATH'] = self.get_build_dir(arch.arch)
return env
diff --git a/p4a/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch b/p4a/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch
new file mode 100644
index 0000000..f39d9b3
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch
@@ -0,0 +1,28 @@
+--- tflite-runtime/tensorflow/lite/CMakeLists.txt 2022-01-27 17:29:49.460000000 -1000
++++ CMakeLists.txt 2022-02-21 15:03:09.568367300 -1000
+@@ -220,6 +220,9 @@
+ if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "iOS")
+ list(FILTER TFLITE_SRCS EXCLUDE REGEX ".*minimal_logging_ios\\.cc$")
+ endif()
++if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android")
++ list(FILTER TFLITE_SRCS EXCLUDE REGEX ".*minimal_logging_default\\.cc$")
++endif()
+ populate_tflite_source_vars("core" TFLITE_CORE_SRCS)
+ populate_tflite_source_vars("core/api" TFLITE_CORE_API_SRCS)
+ populate_tflite_source_vars("c" TFLITE_C_SRCS)
+@@ -505,6 +508,7 @@
+ ruy
+ ${CMAKE_DL_LIBS}
+ ${TFLITE_TARGET_DEPENDENCIES}
++ ${ANDROID_LOG_LIB}
+ )
+
+ if (NOT BUILD_SHARED_LIBS)
+@@ -550,6 +554,7 @@
+ tensorflow-lite
+ ${CMAKE_DL_LIBS}
+ )
++
+ target_compile_options(_pywrap_tensorflow_interpreter_wrapper
+ PUBLIC ${TFLITE_TARGET_PUBLIC_OPTIONS}
+ PRIVATE ${TFLITE_TARGET_PRIVATE_OPTIONS}
diff --git a/p4a/pythonforandroid/recipes/tflite-runtime/__init__.py b/p4a/pythonforandroid/recipes/tflite-runtime/__init__.py
new file mode 100644
index 0000000..1d20886
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/tflite-runtime/__init__.py
@@ -0,0 +1,108 @@
+from pythonforandroid.recipe import PythonRecipe, current_directory, \
+ shprint, info_main, warning
+from pythonforandroid.logger import error
+from os.path import join
+import sh
+
+
+class TFLiteRuntimeRecipe(PythonRecipe):
+ ###############################################################
+ #
+ # tflite-runtime README:
+ # https://github.com/Android-for-Python/c4k_tflite_example/blob/main/README.md
+ #
+ # Recipe build references:
+ # https://developer.android.com/ndk/guides/cmake
+ # https://developer.android.com/ndk/guides/cpu-arm-neon#cmake
+ # https://www.tensorflow.org/lite/guide/build_cmake
+ # https://www.tensorflow.org/lite/guide/build_cmake_arm
+ #
+ # Tested using cmake 3.16.3 probably requires cmake >= 3.13
+ #
+ # THIS RECIPE DOES NOT BUILD x86_64, USE X86 FOR AN EMULATOR
+ #
+ ###############################################################
+
+ version = '2.8.0'
+ url = 'https://github.com/tensorflow/tensorflow/archive/refs/tags/v{version}.zip'
+ depends = ['pybind11', 'numpy']
+ patches = ['CMakeLists.patch', 'build_with_cmake.patch']
+ site_packages_name = 'tflite-runtime'
+ call_hostpython_via_targetpython = False
+
+ def should_build(self, arch):
+ name = self.folder_name.replace('-', '_')
+
+ if self.ctx.has_package(name, arch):
+ info_main('Python package already exists in site-packages')
+ return False
+ info_main('{} apparently isn\'t already in site-packages'.format(name))
+ return True
+
+ def build_arch(self, arch):
+ if arch.arch == 'x86_64':
+ warning("******** tflite-runtime x86_64 will not be built *******")
+ warning("Expect one of these app run time error messages:")
+ warning("ModuleNotFoundError: No module named 'tensorflow'")
+ warning("ModuleNotFoundError: No module named 'tflite_runtime'")
+ warning("Use x86 not x86_64")
+ return
+
+ env = self.get_recipe_env(arch)
+
+ # Directories
+ root_dir = self.get_build_dir(arch.arch)
+ script_dir = join(root_dir,
+ 'tensorflow', 'lite', 'tools', 'pip_package')
+ build_dir = join(script_dir, 'gen', 'tflite_pip', 'python3')
+
+ # Includes
+ python_include_dir = self.ctx.python_recipe.include_root(arch.arch)
+ pybind11_recipe = self.get_recipe('pybind11', self.ctx)
+ pybind11_include_dir = pybind11_recipe.get_include_dir(arch)
+ numpy_include_dir = join(self.ctx.get_site_packages_dir(arch),
+ 'numpy', 'core', 'include')
+ includes = ' -I' + python_include_dir + \
+ ' -I' + numpy_include_dir + \
+ ' -I' + pybind11_include_dir
+
+ # Scripts
+ build_script = join(script_dir, 'build_pip_package_with_cmake.sh')
+ toolchain = join(self.ctx.ndk_dir,
+ 'build', 'cmake', 'android.toolchain.cmake')
+
+ # Build
+ ########
+ with current_directory(root_dir):
+ env.update({
+ 'TENSORFLOW_TARGET': 'android',
+ 'CMAKE_TOOLCHAIN_FILE': toolchain,
+ 'ANDROID_PLATFORM': str(self.ctx.ndk_api),
+ 'ANDROID_ABI': arch.arch,
+ 'WRAPPER_INCLUDES': includes,
+ 'CMAKE_SHARED_LINKER_FLAGS': env['LDFLAGS'],
+ })
+
+ try:
+ info_main('tflite-runtime is building...')
+ info_main('Expect this to take at least 5 minutes...')
+ cmd = sh.Command(build_script)
+ cmd(_env=env)
+ except sh.ErrorReturnCode as e:
+ error(str(e.stderr))
+ exit(1)
+
+ # Install
+ ##########
+ info_main('Installing tflite-runtime into site-packages')
+ with current_directory(build_dir):
+ hostpython = sh.Command(self.hostpython_location)
+ install_dir = self.ctx.get_python_install_dir(arch.arch)
+ env['PACKAGE_VERSION'] = self.version
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(install_dir),
+ '--install-lib=.',
+ _env=env)
+
+
+recipe = TFLiteRuntimeRecipe()
diff --git a/p4a/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch b/p4a/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch
new file mode 100644
index 0000000..9670e18
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch
@@ -0,0 +1,48 @@
+--- tflite-runtime/tensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh 2022-01-22 08:57:16.000000000 -1000
++++ build_pip_package_with_cmake.sh 2022-03-02 18:19:05.185550500 -1000
+@@ -28,7 +28,7 @@
+ export TENSORFLOW_TARGET="armhf"
+ fi
+ PYTHON_INCLUDE=$(${PYTHON} -c "from sysconfig import get_paths as gp; print(gp()['include'])")
+-PYBIND11_INCLUDE=$(${PYTHON} -c "import pybind11; print (pybind11.get_include())")
++# PYBIND11_INCLUDE=$(${PYTHON} -c "import pybind11; print (pybind11.get_include())")
+ export CROSSTOOL_PYTHON_INCLUDE_PATH=${PYTHON_INCLUDE}
+
+ # Fix container image for cross build.
+@@ -58,7 +58,7 @@
+ "${TENSORFLOW_LITE_DIR}/python/metrics/metrics_portable.py" \
+ "${BUILD_DIR}/tflite_runtime"
+ echo "__version__ = '${PACKAGE_VERSION}'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
+-echo "__git_version__ = '$(git -C "${TENSORFLOW_DIR}" describe)'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
++echo "__git_version__ = '${PACKAGE_VERSION}'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
+
+ # Build python interpreter_wrapper.
+ mkdir -p "${BUILD_DIR}/cmake_build"
+@@ -111,6 +111,18 @@
+ -DCMAKE_CXX_FLAGS="${BUILD_FLAGS}" \
+ "${TENSORFLOW_LITE_DIR}"
+ ;;
++ android)
++ BUILD_FLAGS=${BUILD_FLAGS:-"${WRAPPER_INCLUDES}"}
++ cmake \
++ -DCMAKE_SYSTEM_NAME=Android \
++ -DANDROID_ARM_NEON=ON \
++ -DCMAKE_CXX_FLAGS="${BUILD_FLAGS}" \
++ -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \
++ -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}" \
++ -DANDROID_PLATFORM="${ANDROID_PLATFORM}" \
++ -DANDROID_ABI="${ANDROID_ABI}" \
++ "${TENSORFLOW_LITE_DIR}"
++ ;;
+ *)
+ BUILD_FLAGS=${BUILD_FLAGS:-"-I${PYTHON_INCLUDE} -I${PYBIND11_INCLUDE}"}
+ cmake \
+@@ -162,7 +174,7 @@
+ ${PYTHON} setup.py bdist --plat-name=${WHEEL_PLATFORM_NAME} \
+ bdist_wheel --plat-name=${WHEEL_PLATFORM_NAME}
+ else
+- ${PYTHON} setup.py bdist bdist_wheel
++ ${PYTHON} setup.py bdist
+ fi
+ ;;
+ esac
diff --git a/p4a/pythonforandroid/recipes/twisted/__init__.py b/p4a/pythonforandroid/recipes/twisted/__init__.py
index ca22279..0c390a5 100644
--- a/p4a/pythonforandroid/recipes/twisted/__init__.py
+++ b/p4a/pythonforandroid/recipes/twisted/__init__.py
@@ -1,26 +1,35 @@
+import os
+import shutil
+
from pythonforandroid.recipe import CythonRecipe
class TwistedRecipe(CythonRecipe):
- version = '17.9.0'
+ version = '20.3.0'
url = 'https://github.com/twisted/twisted/archive/twisted-{version}.tar.gz'
depends = ['setuptools', 'zope_interface', 'incremental', 'constantly']
- patches = ['incremental.patch']
+ patches = ['incremental.patch', 'remove_tests.patch']
call_hostpython_via_targetpython = False
install_in_hostpython = False
def prebuild_arch(self, arch):
- super(TwistedRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
# TODO Need to whitelist tty.pyo and termios.so here
- print('Should remove twisted tests etc. here, but skipping for now')
+
+ # remove the unit test dirs
+ source_dir = os.path.join(self.get_build_dir(arch.arch), 'src/twisted')
+ for item in os.walk(source_dir):
+ if os.path.basename(item[0]) == 'test':
+ full_path = os.path.join(source_dir, item[0])
+ shutil.rmtree(full_path, ignore_errors=True)
def get_recipe_env(self, arch):
- env = super(TwistedRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# We add BUILDLIB_PATH to PYTHONPATH so twisted can find _io.so
env['PYTHONPATH'] = ':'.join([
- self.ctx.get_site_packages_dir(),
+ self.ctx.get_site_packages_dir(arch),
env['BUILDLIB_PATH'],
])
return env
diff --git a/p4a/pythonforandroid/recipes/twisted/incremental.patch b/p4a/pythonforandroid/recipes/twisted/incremental.patch
index 85e5307..61656fc 100644
--- a/p4a/pythonforandroid/recipes/twisted/incremental.patch
+++ b/p4a/pythonforandroid/recipes/twisted/incremental.patch
@@ -1,13 +1,15 @@
-diff -Naur twisted-twisted-17.9.0/src/twisted/python/_setup.py twisted-twisted-17.9.0_patched/src/twisted/python/_setup.py
---- twisted-twisted-17.9.0/src/twisted/python/_setup.py 2017-09-23 07:56:08.000000000 +0200
-+++ twisted-twisted-17.9.0_patched/src/twisted/python/_setup.py 2018-10-05 11:06:23.305860722 +0200
-@@ -227,14 +227,11 @@
- requirements = ["zope.interface >= 3.6.0"]
-
- requirements.append("constantly >= 15.1")
-- requirements.append("incremental >= 16.10.1")
- requirements.append("Automat >= 0.3.0")
- requirements.append("hyperlink >= 17.1.1")
+diff -Naur twisted-twisted-19.7.0/src/twisted/python/_setup.py twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py
+--- twisted-twisted-19.7.0/src/twisted/python/_setup.py 2019-07-28 11:17:29.000000000 +0200
++++ twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py 2019-10-21 22:10:03.643068863 +0200
+@@ -282,7 +282,6 @@
+ requirements = [
+ "zope.interface >= 4.4.2",
+ "constantly >= 15.1",
+- "incremental >= 16.10.1",
+ "Automat >= 0.3.0",
+ "hyperlink >= 17.1.1",
+ "PyHamcrest >= 1.9.0",
+@@ -291,8 +290,6 @@
arguments.update(dict(
packages=find_packages("src"),
diff --git a/p4a/pythonforandroid/recipes/twisted/remove_tests.patch b/p4a/pythonforandroid/recipes/twisted/remove_tests.patch
new file mode 100644
index 0000000..492062b
--- /dev/null
+++ b/p4a/pythonforandroid/recipes/twisted/remove_tests.patch
@@ -0,0 +1,16 @@
+diff --git a/src/twisted/python/_setup.py b/src/twisted/python/_setup.py
+index 32cb096c7..a607fef07 100644
+--- a/src/twisted/python/_setup.py
++++ b/src/twisted/python/_setup.py
+@@ -160,11 +160,6 @@ class ConditionalExtension(Extension, object):
+
+ # The C extensions used for Twisted.
+ _EXTENSIONS = [
+- ConditionalExtension(
+- "twisted.test.raiser",
+- sources=["src/twisted/test/raiser.c"],
+- condition=lambda _: _isCPython),
+-
+ ConditionalExtension(
+ "twisted.internet.iocpreactor.iocpsupport",
+ sources=[
diff --git a/p4a/pythonforandroid/recipes/vlc/__init__.py b/p4a/pythonforandroid/recipes/vlc/__init__.py
index 66f51b9..490c4f2 100644
--- a/p4a/pythonforandroid/recipes/vlc/__init__.py
+++ b/p4a/pythonforandroid/recipes/vlc/__init__.py
@@ -18,7 +18,7 @@ class VlcRecipe(Recipe):
aars = {} # for future use of multiple arch
def prebuild_arch(self, arch):
- super(VlcRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
build_dir = self.get_build_dir(arch.arch)
port_dir = join(build_dir, 'vlc-port-android')
if self.ENV_LIBVLC_AAR in environ:
@@ -50,7 +50,7 @@ class VlcRecipe(Recipe):
# _tail=20, _critical=True)
def build_arch(self, arch):
- super(VlcRecipe, self).build_arch(arch)
+ super().build_arch(arch)
build_dir = self.get_build_dir(arch.arch)
port_dir = join(build_dir, 'vlc-port-android')
aar = self.aars[arch]
diff --git a/p4a/pythonforandroid/recipes/websocket-client/__init__.py b/p4a/pythonforandroid/recipes/websocket-client/__init__.py
deleted file mode 100644
index 8a3b8ad..0000000
--- a/p4a/pythonforandroid/recipes/websocket-client/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from pythonforandroid.toolchain import Recipe
-
-# if android app crashes on start with "ImportError: No module named websocket"
-#
-# copy the 'websocket' directory into your app directory to force inclusion.
-#
-# see my example at https://github.com/debauchery1st/example_kivy_websocket-recipe
-#
-# If you see errors relating to 'SSL not available' ensure you have the package backports.ssl-match-hostname
-# in the buildozer requirements, since Kivy targets python 2.7.x
-#
-# You may also need sslopt={"cert_reqs": ssl.CERT_NONE} as a parameter to ws.run_forever() if you get an error relating to
-# host verification
-
-
-class WebSocketClient(Recipe):
-
- url = 'https://github.com/websocket-client/websocket-client/archive/v{version}.tar.gz'
-
- version = '0.40.0'
-
- # patches = ['websocket.patch'] # Paths relative to the recipe dir
-
- depends = ['android', 'pyjnius', 'cryptography', 'pyasn1', 'pyopenssl']
-
-
-recipe = WebSocketClient()
diff --git a/p4a/pythonforandroid/recipes/websocket-client/websocket.patch b/p4a/pythonforandroid/recipes/websocket-client/websocket.patch
deleted file mode 100644
index 694bcb6..0000000
--- a/p4a/pythonforandroid/recipes/websocket-client/websocket.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff --git a/websocket/_logging.py b/websocket/_logging.py
-index 8a5f4a5..cebc23b 100644
---- a/websocket/_logging.py
-+++ b/websocket/_logging.py
-@@ -19,9 +19,8 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
- Boston, MA 02110-1335 USA
-
- """
--import logging
--
--_logger = logging.getLogger('websocket')
-+from kivy.logger import Logger
-+_logger = Logger
- _traceEnabled = False
-
- __all__ = ["enableTrace", "dump", "error", "debug", "trace",
-@@ -67,8 +66,9 @@ def trace(msg):
-
-
- def isEnabledForError():
-- return _logger.isEnabledFor(logging.ERROR)
-+ return True
-
-
- def isEnabledForDebug():
-- return _logger.isEnabledFor(logging.DEBUG)
-+ return True
-+
diff --git a/p4a/pythonforandroid/recipes/xeddsa/__init__.py b/p4a/pythonforandroid/recipes/xeddsa/__init__.py
index eb0e2ae..d386f92 100644
--- a/p4a/pythonforandroid/recipes/xeddsa/__init__.py
+++ b/p4a/pythonforandroid/recipes/xeddsa/__init__.py
@@ -24,12 +24,9 @@ class XedDSARecipe(CythonRecipe):
hostpython, 'ref10/build.py',
_env=env
)
- python_version = self.ctx.python_recipe.version[0:3]
- site_packages_dir = 'lib/python{python_version}/site-packages'.format(
- python_version=python_version)
- site_packages = join(self.ctx.get_python_install_dir(),
- site_packages_dir)
- shprint(sh.cp, '_crypto_sign.so', site_packages)
+ # the library could be `_crypto_sign.cpython-37m-x86_64-linux-gnu.so`
+ # or simply `_crypto_sign.so` depending on the platform/distribution
+ sh.cp('-a', sh.glob('_crypto_sign*.so'), self.ctx.get_site_packages_dir(arch))
self.install_python_package(arch)
diff --git a/p4a/pythonforandroid/recipes/zbar/__init__.py b/p4a/pythonforandroid/recipes/zbar/__init__.py
index 62aa85b..c24971e 100644
--- a/p4a/pythonforandroid/recipes/zbar/__init__.py
+++ b/p4a/pythonforandroid/recipes/zbar/__init__.py
@@ -20,10 +20,10 @@ class ZBarRecipe(PythonRecipe):
patches = ["zbar-0.10-python-crash.patch"]
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(ZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
libzbar = self.get_recipe('libzbar', self.ctx)
libzbar_dir = libzbar.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
diff --git a/p4a/pythonforandroid/recipes/zbarlight/__init__.py b/p4a/pythonforandroid/recipes/zbarlight/__init__.py
index 966c7fb..36365cd 100644
--- a/p4a/pythonforandroid/recipes/zbarlight/__init__.py
+++ b/p4a/pythonforandroid/recipes/zbarlight/__init__.py
@@ -13,10 +13,10 @@ class ZBarLightRecipe(PythonRecipe):
depends = ['setuptools', 'libzbar']
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(ZBarLightRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
libzbar = self.get_recipe('libzbar', self.ctx)
libzbar_dir = libzbar.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
diff --git a/p4a/pythonforandroid/recipes/zeroconf/__init__.py b/p4a/pythonforandroid/recipes/zeroconf/__init__.py
index 5ca5708..a23bd6e 100644
--- a/p4a/pythonforandroid/recipes/zeroconf/__init__.py
+++ b/p4a/pythonforandroid/recipes/zeroconf/__init__.py
@@ -3,9 +3,9 @@ from pythonforandroid.recipe import PythonRecipe
class ZeroconfRecipe(PythonRecipe):
name = 'zeroconf'
- version = '0.17.4'
+ version = '0.24.5'
url = 'https://pypi.python.org/packages/source/z/zeroconf/zeroconf-{version}.tar.gz'
- depends = ['setuptools', 'enum34', 'six']
+ depends = ['setuptools', 'ifaddr', 'typing;python_version<"3.5"']
call_hostpython_via_targetpython = False
diff --git a/p4a/pythonforandroid/recipes/zope/__init__.py b/p4a/pythonforandroid/recipes/zope/__init__.py
index 579a760..9c5ab7b 100644
--- a/p4a/pythonforandroid/recipes/zope/__init__.py
+++ b/p4a/pythonforandroid/recipes/zope/__init__.py
@@ -6,22 +6,25 @@ from os.path import join
class ZopeRecipe(PythonRecipe):
name = 'zope'
version = '4.1.3'
- url = 'http://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
+ url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
depends = []
def get_recipe_env(self, arch):
- env = super(ZopeRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# These are in the old zope recipe but seem like they shouldn't actually be necessary
env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
self.ctx.get_libs_dir(arch.arch))
env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
+ return env
def postbuild_arch(self, arch):
- super(ZopeRecipe, self).postbuild_arch(arch)
+ super().postbuild_arch(arch)
# Should do some deleting here
recipe = ZopeRecipe()
+
+# FIXME: @mirko liblink & LD
diff --git a/p4a/pythonforandroid/recipes/zope_interface/__init__.py b/p4a/pythonforandroid/recipes/zope_interface/__init__.py
index b1fb0bd..46a1820 100644
--- a/p4a/pythonforandroid/recipes/zope_interface/__init__.py
+++ b/p4a/pythonforandroid/recipes/zope_interface/__init__.py
@@ -14,18 +14,22 @@ class ZopeInterfaceRecipe(PythonRecipe):
patches = ['no_tests.patch']
def build_arch(self, arch):
- super(ZopeInterfaceRecipe, self).build_arch(arch)
+ super().build_arch(arch)
# The zope.interface module lacks of the __init__.py file in one of his
# folders (once is installed), that leads into an ImportError.
# Here we intentionally apply a patch to solve that, so, in case that
# this is solved in the future an error will be triggered
- zope_install = join(self.ctx.get_site_packages_dir(arch.arch), 'zope')
+ zope_install = join(self.ctx.get_site_packages_dir(arch), 'zope')
self.apply_patch('fix-init.patch', arch.arch, build_dir=zope_install)
def prebuild_arch(self, arch):
- super(ZopeInterfaceRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
- sh.rm('-rf', 'src/zope/interface/tests', 'src/zope/interface/common/tests')
+ sh.rm(
+ '-rf',
+ 'src/zope/interface/tests',
+ 'src/zope/interface/common/tests',
+ )
recipe = ZopeInterfaceRecipe()
diff --git a/p4a/pythonforandroid/recommendations.py b/p4a/pythonforandroid/recommendations.py
index fd2fd3a..040c962 100644
--- a/p4a/pythonforandroid/recommendations.py
+++ b/p4a/pythonforandroid/recommendations.py
@@ -1,37 +1,115 @@
"""Simple functions for checking dependency versions."""
+import sys
from distutils.version import LooseVersion
from os.path import join
+
from pythonforandroid.logger import info, warning
from pythonforandroid.util import BuildInterruptingException
# We only check the NDK major version
-MIN_NDK_VERSION = 17
-MAX_NDK_VERSION = 17
+MIN_NDK_VERSION = 25
+MAX_NDK_VERSION = 25
-RECOMMENDED_NDK_VERSION = '17c'
-OLD_NDK_MESSAGE = 'Older NDKs may not be compatible with all p4a features.'
+# DO NOT CHANGE LINE FORMAT: buildozer parses the existence of a RECOMMENDED_NDK_VERSION
+RECOMMENDED_NDK_VERSION = "25b"
+
+NDK_DOWNLOAD_URL = "https://developer.android.com/ndk/downloads/"
+
+# Important log messages
NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.'
+UNKNOWN_NDK_MESSAGE = (
+ 'Could not determine NDK version, no source.properties in the NDK dir.'
+)
+PARSE_ERROR_NDK_MESSAGE = (
+ 'Could not parse $NDK_DIR/source.properties, not checking NDK version.'
+)
+READ_ERROR_NDK_MESSAGE = (
+ 'Unable to read the NDK version from the given directory {ndk_dir}.'
+)
+ENSURE_RIGHT_NDK_MESSAGE = (
+ 'Make sure your NDK version is greater than {min_supported}. If you get '
+ 'build errors, download the recommended NDK {rec_version} from {ndk_url}.'
+)
+NDK_LOWER_THAN_SUPPORTED_MESSAGE = (
+ 'The minimum supported NDK version is {min_supported}. '
+ 'You can download it from {ndk_url}.'
+)
+UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE = (
+ 'Asked to build for armeabi architecture with API '
+ '{req_ndk_api}, but API {max_ndk_api} or greater does not support armeabi.'
+)
+CURRENT_NDK_VERSION_MESSAGE = (
+ 'Found NDK version {ndk_version}'
+)
+RECOMMENDED_NDK_VERSION_MESSAGE = (
+ 'Maximum recommended NDK version is {recommended_ndk_version}, but newer versions may work.'
+)
def check_ndk_version(ndk_dir):
- # Check the NDK version against what is currently recommended
+ """
+ Check the NDK version against what is currently recommended and raise an
+ exception of :class:`~pythonforandroid.util.BuildInterruptingException` in
+ case that the user tries to use an NDK lower than minimum supported,
+ specified via attribute `MIN_NDK_VERSION`.
+
+ .. versionchanged:: 2019.06.06.1.dev0
+ Added the ability to get android's NDK `letter version` and also
+ rewrote to raise an exception in case that an NDK version lower than
+ the minimum supported is detected.
+ """
version = read_ndk_version(ndk_dir)
if version is None:
- return # if we failed to read the version, just don't worry about it
+ warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir))
+ warning(
+ ENSURE_RIGHT_NDK_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION,
+ rec_version=RECOMMENDED_NDK_VERSION,
+ ndk_url=NDK_DOWNLOAD_URL,
+ )
+ )
+ return
+
+ # create a dictionary which will describe the relationship of the android's
+ # NDK minor version with the `human readable` letter version, egs:
+ # Pkg.Revision = 17.1.4828580 => ndk-17b
+ # Pkg.Revision = 17.2.4988734 => ndk-17c
+ # Pkg.Revision = 19.0.5232133 => ndk-19 (No letter)
+ minor_to_letter = {0: ''}
+ minor_to_letter.update(
+ {n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))}
+ )
major_version = version.version[0]
+ letter_version = minor_to_letter[version.version[1]]
+ string_version = '{major_version}{letter_version}'.format(
+ major_version=major_version, letter_version=letter_version
+ )
- info('Found NDK revision {}'.format(version))
+ info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version))
if major_version < MIN_NDK_VERSION:
- warning('Minimum recommended NDK version is {}'.format(
- RECOMMENDED_NDK_VERSION))
- warning(OLD_NDK_MESSAGE)
+ raise BuildInterruptingException(
+ NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL
+ ),
+ instructions=(
+ 'Please, go to the android NDK page ({ndk_url}) and download a'
+ ' supported version.\n*** The currently recommended NDK'
+ ' version is {rec_version} ***'.format(
+ ndk_url=NDK_DOWNLOAD_URL,
+ rec_version=RECOMMENDED_NDK_VERSION,
+ )
+ ),
+ )
elif major_version > MAX_NDK_VERSION:
- warning('Maximum recommended NDK version is {}'.format(
- RECOMMENDED_NDK_VERSION))
+ warning(
+ RECOMMENDED_NDK_VERSION_MESSAGE.format(
+ recommended_ndk_version=RECOMMENDED_NDK_VERSION
+ )
+ )
warning(NEW_NDK_MESSAGE)
@@ -41,16 +119,14 @@ def read_ndk_version(ndk_dir):
with open(join(ndk_dir, 'source.properties')) as fileh:
ndk_data = fileh.read()
except IOError:
- info('Could not determine NDK version, no source.properties '
- 'in the NDK dir')
+ info(UNKNOWN_NDK_MESSAGE)
return
for line in ndk_data.split('\n'):
if line.startswith('Pkg.Revision'):
break
else:
- info('Could not parse $NDK_DIR/source.properties, not checking '
- 'NDK version')
+ info(PARSE_ERROR_NDK_MESSAGE)
return
# Line should have the form "Pkg.Revision = ..."
@@ -59,15 +135,15 @@ def read_ndk_version(ndk_dir):
return ndk_version
-MIN_TARGET_API = 26
+MIN_TARGET_API = 30
# highest version tested to work fine with SDL2
# should be a good default for other bootstraps too
-RECOMMENDED_TARGET_API = 27
+RECOMMENDED_TARGET_API = 33
ARMEABI_MAX_TARGET_API = 21
OLD_API_MESSAGE = (
- 'Target APIs lower than 26 are no longer supported on Google Play, '
+ 'Target APIs lower than 30 are no longer supported on Google Play, '
'and are not recommended. Note that the Target API can be higher than '
'your device Android version, and should usually be as high as possible.')
@@ -77,11 +153,12 @@ def check_target_api(api, arch):
recommendation
"""
+ # FIXME: Should We remove support for armeabi (ARMv5)?
if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':
raise BuildInterruptingException(
- 'Asked to build for armeabi architecture with API '
- '{}, but API {} or greater does not support armeabi'.format(
- api, ARMEABI_MAX_TARGET_API),
+ UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE.format(
+ req_ndk_api=api, max_ndk_api=ARMEABI_MAX_TARGET_API
+ ),
instructions='You probably want to build with --arch=armeabi-v7a instead')
if api < MIN_TARGET_API:
@@ -92,16 +169,65 @@ def check_target_api(api, arch):
MIN_NDK_API = 21
RECOMMENDED_NDK_API = 21
OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API))
+TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE = (
+ 'Target NDK API is {ndk_api}, '
+ 'higher than the target Android API {android_api}.'
+)
def check_ndk_api(ndk_api, android_api):
"""Warn if the user's NDK is too high or low."""
if ndk_api > android_api:
raise BuildInterruptingException(
- 'Target NDK API is {}, higher than the target Android API {}.'.format(
- ndk_api, android_api),
+ TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE.format(
+ ndk_api=ndk_api, android_api=android_api
+ ),
instructions=('The NDK API is a minimum supported API number and must be lower '
'than the target Android API'))
if ndk_api < MIN_NDK_API:
warning(OLD_NDK_API_MESSAGE)
+
+
+MIN_PYTHON_MAJOR_VERSION = 3
+MIN_PYTHON_MINOR_VERSION = 6
+MIN_PYTHON_VERSION = LooseVersion('{major}.{minor}'.format(major=MIN_PYTHON_MAJOR_VERSION,
+ minor=MIN_PYTHON_MINOR_VERSION))
+PY2_ERROR_TEXT = (
+ 'python-for-android no longer supports running under Python 2. Either upgrade to '
+ 'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.'
+).format(min_version=MIN_PYTHON_VERSION)
+
+PY_VERSION_ERROR_TEXT = (
+ 'Your Python version {user_major}.{user_minor} is not supported by python-for-android, '
+ 'please upgrade to {min_version} or higher.'
+ ).format(
+ user_major=sys.version_info.major,
+ user_minor=sys.version_info.minor,
+ min_version=MIN_PYTHON_VERSION)
+
+
+def check_python_version():
+ # Python 2 special cased because it's a major transition. In the
+ # future the major or minor versions can increment more quietly.
+ if sys.version_info.major == 2:
+ raise BuildInterruptingException(PY2_ERROR_TEXT)
+
+ if (
+ sys.version_info.major < MIN_PYTHON_MAJOR_VERSION or
+ sys.version_info.minor < MIN_PYTHON_MINOR_VERSION
+ ):
+
+ raise BuildInterruptingException(PY_VERSION_ERROR_TEXT)
+
+
+def print_recommendations():
+ """
+ Print the main recommended dependency versions as simple key-value pairs.
+ """
+ print('Min supported NDK version: {}'.format(MIN_NDK_VERSION))
+ print('Recommended NDK version: {}'.format(RECOMMENDED_NDK_VERSION))
+ print('Min target API: {}'.format(MIN_TARGET_API))
+ print('Recommended target API: {}'.format(RECOMMENDED_TARGET_API))
+ print('Min NDK API: {}'.format(MIN_NDK_API))
+ print('Recommended NDK API: {}'.format(RECOMMENDED_NDK_API))
diff --git a/p4a/pythonforandroid/toolchain.py b/p4a/pythonforandroid/toolchain.py
index ddf745a..a9ab905 100644
--- a/p4a/pythonforandroid/toolchain.py
+++ b/p4a/pythonforandroid/toolchain.py
@@ -6,12 +6,14 @@ Tool for packaging Python apps for Android
This module defines the entry point for command line and programmatic use.
"""
-from __future__ import print_function
from os import environ
from pythonforandroid import __version__
+from pythonforandroid.pythonpackage import get_dep_names_of_package
from pythonforandroid.recommendations import (
- RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
-from pythonforandroid.util import BuildInterruptingException, handle_build_exception
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations)
+from pythonforandroid.util import BuildInterruptingException, load_source
+from pythonforandroid.entrypoints import main
+from pythonforandroid.prerequisites import check_and_install_default_prerequisites
def check_python_dependencies():
@@ -27,8 +29,7 @@ def check_python_dependencies():
ok = True
- modules = [('colorama', '0.3.3'), 'appdirs', ('sh', '1.10'), 'jinja2',
- 'six']
+ modules = [('colorama', '0.3.3'), 'appdirs', ('sh', '1.10'), 'jinja2']
for module in modules:
if isinstance(module, tuple):
@@ -65,6 +66,8 @@ def check_python_dependencies():
exit(1)
+if not environ.get('SKIP_PREREQUISITES_CHECK', '0') == '1':
+ check_and_install_default_prerequisites()
check_python_dependencies()
@@ -80,7 +83,6 @@ from functools import wraps
import argparse
import sh
-import imp
from appdirs import user_data_dir
import logging
from distutils.version import LooseVersion
@@ -135,7 +137,7 @@ def require_prebuilt_dist(func):
"""
@wraps(func)
- def wrapper_func(self, args):
+ def wrapper_func(self, args, **kw):
ctx = self.ctx
ctx.set_archs(self._archs)
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
@@ -149,7 +151,7 @@ def require_prebuilt_dist(func):
info_notify('No dist exists that meets your requirements, '
'so one will be built.')
build_dist_from_args(ctx, dist, args)
- func(self, args)
+ func(self, args, **kw)
return wrapper_func
@@ -161,6 +163,7 @@ def dist_from_args(ctx, args):
ctx,
name=args.dist_name,
recipes=split_argument_list(args.requirements),
+ archs=args.arch,
ndk_api=args.ndk_api,
force_build=args.force_build,
require_perfect_match=args.require_perfect_match,
@@ -171,37 +174,59 @@ def build_dist_from_args(ctx, dist, args):
"""Parses out any bootstrap related arguments, and uses them to build
a dist."""
bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
- build_order, python_modules, bs \
- = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs)
+ blacklist = getattr(args, "blacklist_requirements", "").split(",")
+ if len(blacklist) == 1 and blacklist[0] == "":
+ blacklist = []
+ build_order, python_modules, bs = (
+ get_recipe_order_and_bootstrap(
+ ctx, dist.recipes, bs,
+ blacklist=blacklist
+ ))
+ assert set(build_order).intersection(set(python_modules)) == set()
ctx.recipe_build_order = build_order
ctx.python_modules = python_modules
info('The selected bootstrap is {}'.format(bs.name))
info_main('# Creating dist with {} bootstrap'.format(bs.name))
bs.distribution = dist
- info_notify('Dist will have name {} and recipes ({})'.format(
+ info_notify('Dist will have name {} and requirements ({})'.format(
dist.name, ', '.join(dist.recipes)))
+ info('Dist contains the following requirements as recipes: {}'.format(
+ ctx.recipe_build_order))
info('Dist will also contain modules ({}) installed from pip'.format(
', '.join(ctx.python_modules)))
+ info(
+ 'Dist will be build in mode {build_mode}{with_debug_symbols}'.format(
+ build_mode='debug' if ctx.build_as_debuggable else 'release',
+ with_debug_symbols=' (with debug symbols)'
+ if ctx.with_debug_symbols
+ else '',
+ )
+ )
- ctx.dist_name = bs.distribution.name
+ ctx.distribution = dist
ctx.prepare_bootstrap(bs)
if dist.needs_build:
- ctx.prepare_dist(ctx.dist_name)
+ ctx.prepare_dist()
- build_recipes(build_order, python_modules, ctx)
+ build_recipes(build_order, python_modules, ctx,
+ getattr(args, "private", None),
+ ignore_project_setup_py=getattr(
+ args, "ignore_setup_py", False
+ ),
+ )
- ctx.bootstrap.run_distribute()
+ ctx.bootstrap.assemble_distribution()
info_main('# Your distribution was created successfully, exiting.')
info('Dist can be found at (for now) {}'
- .format(join(ctx.dist_dir, ctx.dist_name)))
+ .format(join(ctx.dist_dir, ctx.distribution.dist_dir)))
-def split_argument_list(l):
- if not len(l):
+def split_argument_list(arg_list):
+ if not len(arg_list):
return []
- return re.split(r'[ ,]+', l)
+ return re.split(r'[ ,]+', arg_list)
class NoAbbrevParser(argparse.ArgumentParser):
@@ -216,11 +241,12 @@ class NoAbbrevParser(argparse.ArgumentParser):
return []
-class ToolchainCL(object):
+class ToolchainCL:
def __init__(self):
argv = sys.argv
+ self.warn_on_carriage_return_args(argv)
# Buildozer used to pass these arguments in a now-invalid order
# If that happens, apply this fix
# This fix will be removed once a fixed buildozer is released
@@ -270,11 +296,11 @@ class ToolchainCL(object):
'*minimal supported* API, not normally the same as your --android-api. '
'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))
generic_parser.add_argument(
- '--symlink-java-src', '--symlink_java_src',
+ '--symlink-bootstrap-files', '--ssymlink_bootstrap_files',
action='store_true',
- dest='symlink_java_src',
+ dest='symlink_bootstrap_files',
default=False,
- help=('If True, symlinks the java src folder during build and dist '
+ help=('If True, symlinks the bootstrap files '
'creation. This is useful for development only, it could also'
' cause weird problems.'))
@@ -287,8 +313,8 @@ class ToolchainCL(object):
'(default: {})'.format(default_storage_dir)))
generic_parser.add_argument(
- '--arch', help='The archs to build for, separated by commas.',
- default='arm64-v8a')
+ '--arch', help='The archs to build for.',
+ action='append', default=[])
# Options for specifying the Distribution
generic_parser.add_argument(
@@ -298,7 +324,22 @@ class ToolchainCL(object):
generic_parser.add_argument(
'--requirements',
help=('Dependencies of your app, should be recipe names or '
- 'Python modules'),
+ 'Python modules. NOT NECESSARY if you are using '
+ 'Python 3 with --use-setup-py'),
+ default='')
+
+ generic_parser.add_argument(
+ '--recipe-blacklist',
+ help=('Blacklist an internal recipe from use. Allows '
+ 'disabling Python 3 core modules to save size'),
+ dest="recipe_blacklist",
+ default='')
+
+ generic_parser.add_argument(
+ '--blacklist-requirements',
+ help=('Blacklist an internal recipe from use. Allows '
+ 'disabling Python 3 core modules to save size'),
+ dest="blacklist_requirements",
default='')
generic_parser.add_argument(
@@ -334,6 +375,16 @@ class ToolchainCL(object):
dest='local_recipes', default='./p4a-recipes',
help='Directory to look for local recipes')
+ generic_parser.add_argument(
+ '--activity-class-name',
+ dest='activity_class_name', default='org.kivy.android.PythonActivity',
+ help='The full java class name of the main activity')
+
+ generic_parser.add_argument(
+ '--service-class-name',
+ dest='service_class_name', default='org.kivy.android.PythonService',
+ help='Full java package name of the PythonService class')
+
generic_parser.add_argument(
'--java-build-tool',
dest='java_build_tool', default='auto',
@@ -361,7 +412,7 @@ class ToolchainCL(object):
kwargs.pop('aliases')
return subparsers.add_parser(*args, **kwargs)
- parser_recommendations = add_parser(
+ add_parser(
subparsers,
'recommendations',
parents=[generic_parser],
@@ -447,43 +498,94 @@ class ToolchainCL(object):
action='store_true',
help='Symlink the dist instead of copying')
- parser_apk = add_parser(
+ parser_packaging = argparse.ArgumentParser(
+ parents=[generic_parser],
+ add_help=False,
+ description='common options for packaging (apk, aar)')
- subparsers,
- 'apk', help='Build an APK',
- parents=[generic_parser])
- parser_apk.add_argument(
+ # This is actually an internal argument of the build.py
+ # (see pythonforandroid/bootstraps/common/build/build.py).
+ # However, it is also needed before the distribution is finally
+ # assembled for locating the setup.py / other build systems, which
+ # is why we also add it here:
+ parser_packaging.add_argument(
+ '--add-asset', dest='assets',
+ action="append", default=[],
+ help='Put this in the assets folder in the apk.')
+ parser_packaging.add_argument(
+ '--add-resource', dest='resources',
+ action="append", default=[],
+ help='Put this in the res folder in the apk.')
+ parser_packaging.add_argument(
+ '--private', dest='private',
+ help='the directory with the app source code files' +
+ ' (containing your main.py entrypoint)',
+ required=False, default=None)
+ parser_packaging.add_argument(
+ '--use-setup-py', dest="use_setup_py",
+ action='store_true', default=False,
+ help="Process the setup.py of a project if present. " +
+ "(Experimental!")
+ parser_packaging.add_argument(
+ '--ignore-setup-py', dest="ignore_setup_py",
+ action='store_true', default=False,
+ help="Don't run the setup.py of a project if present. " +
+ "This may be required if the setup.py is not " +
+ "designed to work inside p4a (e.g. by installing " +
+ "dependencies that won't work or aren't desired " +
+ "on Android")
+ parser_packaging.add_argument(
'--release', dest='build_mode', action='store_const',
const='release', default='debug',
- help='Build the PARSER_APK. in Release mode')
- parser_apk.add_argument(
+ help='Build your app as a non-debug release build. '
+ '(Disables gdb debugging among other things)')
+ parser_packaging.add_argument(
+ '--with-debug-symbols', dest='with_debug_symbols',
+ action='store_const', const=True, default=False,
+ help='Will keep debug symbols from `.so` files.')
+ parser_packaging.add_argument(
'--keystore', dest='keystore', action='store', default=None,
help=('Keystore for JAR signing key, will use jarsigner '
'default if not specified (release build only)'))
- parser_apk.add_argument(
+ parser_packaging.add_argument(
'--signkey', dest='signkey', action='store', default=None,
help='Key alias to sign PARSER_APK. with (release build only)')
- parser_apk.add_argument(
+ parser_packaging.add_argument(
'--keystorepw', dest='keystorepw', action='store', default=None,
help='Password for keystore')
- parser_apk.add_argument(
+ parser_packaging.add_argument(
'--signkeypw', dest='signkeypw', action='store', default=None,
help='Password for key alias')
- parser_create = add_parser(
+ add_parser(
+ subparsers,
+ 'aar', help='Build an AAR',
+ parents=[parser_packaging])
+
+ add_parser(
+ subparsers,
+ 'apk', help='Build an APK',
+ parents=[parser_packaging])
+
+ add_parser(
+ subparsers,
+ 'aab', help='Build an AAB',
+ parents=[parser_packaging])
+
+ add_parser(
subparsers,
'create', help='Compile a set of requirements into a dist',
parents=[generic_parser])
- parser_archs = add_parser(
+ add_parser(
subparsers,
'archs', help='List the available target architectures',
parents=[generic_parser])
- parser_distributions = add_parser(
+ add_parser(
subparsers,
'distributions', aliases=['dists'],
help='List the currently available (compiled) dists',
parents=[generic_parser])
- parser_delete_dist = add_parser(
+ add_parser(
subparsers,
'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
parents=[generic_parser])
@@ -496,15 +598,15 @@ class ToolchainCL(object):
parser_sdk_tools.add_argument(
'tool', help='The binary tool name to run')
- parser_adb = add_parser(
+ add_parser(
subparsers,
'adb', help='Run adb from the given SDK',
parents=[generic_parser])
- parser_logcat = add_parser(
+ add_parser(
subparsers,
'logcat', help='Run logcat from the given SDK',
parents=[generic_parser])
- parser_build_status = add_parser(
+ add_parser(
subparsers,
'build_status', aliases=['build-status'],
help='Print some debug information about current built components',
@@ -516,6 +618,20 @@ class ToolchainCL(object):
args, unknown = parser.parse_known_args(sys.argv[1:])
args.unknown_args = unknown
+ if hasattr(args, "private") and args.private is not None:
+ # Pass this value on to the internal bootstrap build.py:
+ args.unknown_args += ["--private", args.private]
+ if hasattr(args, "build_mode") and args.build_mode == "release":
+ args.unknown_args += ["--release"]
+ if hasattr(args, "with_debug_symbols") and args.with_debug_symbols:
+ args.unknown_args += ["--with-debug-symbols"]
+ if hasattr(args, "ignore_setup_py") and args.ignore_setup_py:
+ args.use_setup_py = False
+ if hasattr(args, "activity_class_name") and args.activity_class_name != 'org.kivy.android.PythonActivity':
+ args.unknown_args += ["--activity-class-name", args.activity_class_name]
+ if hasattr(args, "service_class_name") and args.service_class_name != 'org.kivy.android.PythonService':
+ args.unknown_args += ["--service-class-name", args.service_class_name]
+
self.args = args
if args.subparser_name is None:
@@ -527,9 +643,64 @@ class ToolchainCL(object):
if args.debug:
logger.setLevel(logging.DEBUG)
- # strip version from requirements, and put them in environ
+ self.ctx = Context()
+ self.ctx.use_setup_py = getattr(args, "use_setup_py", True)
+ self.ctx.build_as_debuggable = getattr(
+ args, "build_mode", "debug"
+ ) == "debug"
+ self.ctx.with_debug_symbols = getattr(
+ args, "with_debug_symbols", False
+ )
+
+ have_setup_py_or_similar = False
+ if getattr(args, "private", None) is not None:
+ project_dir = getattr(args, "private")
+ if (os.path.exists(os.path.join(project_dir, "setup.py")) or
+ os.path.exists(os.path.join(project_dir,
+ "pyproject.toml"))):
+ have_setup_py_or_similar = True
+
+ # Process requirements and put version in environ
if hasattr(args, 'requirements'):
requirements = []
+
+ # Add dependencies from setup.py, but only if they are recipes
+ # (because otherwise, setup.py itself will install them later)
+ if (have_setup_py_or_similar and
+ getattr(args, "use_setup_py", False)):
+ try:
+ info("Analyzing package dependencies. MAY TAKE A WHILE.")
+ # Get all the dependencies corresponding to a recipe:
+ dependencies = [
+ dep.lower() for dep in
+ get_dep_names_of_package(
+ args.private,
+ keep_version_pins=True,
+ recursive=True,
+ verbose=True,
+ )
+ ]
+ info("Dependencies obtained: " + str(dependencies))
+ all_recipes = [
+ recipe.lower() for recipe in
+ set(Recipe.list_recipes(self.ctx))
+ ]
+ dependencies = set(dependencies).intersection(
+ set(all_recipes)
+ )
+ # Add dependencies to argument list:
+ if len(dependencies) > 0:
+ if len(args.requirements) > 0:
+ args.requirements += u","
+ args.requirements += u",".join(dependencies)
+ except ValueError:
+ # Not a python package, apparently.
+ warning(
+ "Processing failed, is this project a valid "
+ "package? Will continue WITHOUT setup.py deps."
+ )
+
+ # Parse --requirements argument list:
for requirement in split_argument_list(args.requirements):
if "==" in requirement:
requirement, version = requirement.split(u"==", 1)
@@ -541,29 +712,58 @@ class ToolchainCL(object):
self.warn_on_deprecated_args(args)
- self.ctx = Context()
self.storage_dir = args.storage_dir
self.ctx.setup_dirs(self.storage_dir)
self.sdk_dir = args.sdk_dir
self.ndk_dir = args.ndk_dir
self.android_api = args.android_api
self.ndk_api = args.ndk_api
- self.ctx.symlink_java_src = args.symlink_java_src
+ self.ctx.symlink_bootstrap_files = args.symlink_bootstrap_files
self.ctx.java_build_tool = args.java_build_tool
- self._archs = split_argument_list(args.arch)
+ self._archs = args.arch
- self.ctx.local_recipes = args.local_recipes
+ self.ctx.local_recipes = realpath(args.local_recipes)
self.ctx.copy_libs = args.copy_libs
+ self.ctx.activity_class_name = args.activity_class_name
+ self.ctx.service_class_name = args.service_class_name
+
# Each subparser corresponds to a method
- getattr(self, args.subparser_name.replace('-', '_'))(args)
+ command = args.subparser_name.replace('-', '_')
+ getattr(self, command)(args)
+
+ @staticmethod
+ def warn_on_carriage_return_args(args):
+ for check_arg in args:
+ if '\r' in check_arg:
+ warning("Argument '{}' contains a carriage return (\\r).".format(str(check_arg.replace('\r', ''))))
+ warning("Invoking this program via scripts which use CRLF instead of LF line endings will have undefined behaviour.")
def warn_on_deprecated_args(self, args):
"""
Print warning messages for any deprecated arguments that were passed.
"""
+ # Output warning if setup.py is present and neither --ignore-setup-py
+ # nor --use-setup-py was specified.
+ if getattr(args, "private", None) is not None and \
+ (os.path.exists(os.path.join(args.private, "setup.py")) or
+ os.path.exists(os.path.join(args.private, "pyproject.toml"))
+ ):
+ if not getattr(args, "use_setup_py", False) and \
+ not getattr(args, "ignore_setup_py", False):
+ warning(" **** FUTURE BEHAVIOR CHANGE WARNING ****")
+ warning("Your project appears to contain a setup.py file.")
+ warning("Currently, these are ignored by default.")
+ warning("This will CHANGE in an upcoming version!")
+ warning("")
+ warning("To ensure your setup.py is ignored, please specify:")
+ warning(" --ignore-setup-py")
+ warning("")
+ warning("To enable what will some day be the default, specify:")
+ warning(" --use-setup-py")
+
# NDK version is now determined automatically
if args.ndk_version is not None:
warning('--ndk-version is deprecated and no longer necessary, '
@@ -577,8 +777,8 @@ class ToolchainCL(object):
return
if not hasattr(self, "hook_module"):
# first time, try to load the hook module
- self.hook_module = imp.load_source("pythonforandroid.hook",
- self.args.hook)
+ self.hook_module = load_source(
+ "pythonforandroid.hook", self.args.hook)
if hasattr(self.hook_module, name):
info("Hook: execute {}".format(name))
getattr(self.hook_module, name)(self)
@@ -607,6 +807,14 @@ class ToolchainCL(object):
sys.argv.append(arg)
def recipes(self, args):
+ """
+ Prints recipes basic info, e.g.
+ .. code-block:: bash
+ python3 3.7.1
+ depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
+ conflicts: []
+ optional depends: ['sqlite3', 'libffi', 'openssl']
+ """
ctx = self.ctx
if args.compact:
print(" ".join(set(Recipe.list_recipes(ctx))))
@@ -614,7 +822,7 @@ class ToolchainCL(object):
for name in sorted(Recipe.list_recipes(ctx)):
try:
recipe = Recipe.get_recipe(name, ctx)
- except IOError:
+ except (IOError, ValueError):
warning('Recipe "{}" could not be loaded'.format(name))
except SyntaxError:
import traceback
@@ -640,7 +848,7 @@ class ToolchainCL(object):
def bootstraps(self, _args):
"""List all the bootstraps available to build with."""
- for bs in Bootstrap.list_bootstraps():
+ for bs in Bootstrap.all_bootstraps():
bs = Bootstrap.get_bootstrap(bs, self.ctx)
print('{Fore.BLUE}{Style.BRIGHT}{bs.name}{Style.RESET_ALL}'
.format(bs=bs, Fore=Out_Fore, Style=Out_Style))
@@ -683,7 +891,7 @@ class ToolchainCL(object):
"""Delete all the bootstrap builds."""
if exists(join(self.ctx.build_dir, 'bootstrap_builds')):
shutil.rmtree(join(self.ctx.build_dir, 'bootstrap_builds'))
- # for bs in Bootstrap.list_bootstraps():
+ # for bs in Bootstrap.all_bootstraps():
# bs = Bootstrap.get_bootstrap(bs, self.ctx)
# if bs.build_dir and exists(bs.build_dir):
# info('Cleaning build for {} bootstrap.'.format(bs.name))
@@ -770,22 +978,40 @@ class ToolchainCL(object):
def _dist(self):
ctx = self.ctx
dist = dist_from_args(ctx, self.args)
+ ctx.distribution = dist
return dist
- @require_prebuilt_dist
- def apk(self, args):
- """Create an APK using the given distribution."""
+ @staticmethod
+ def _fix_args(args):
+ """
+ Manually fixing these arguments at the string stage is
+ unsatisfactory and should probably be changed somehow, but
+ we can't leave it until later as the build.py scripts assume
+ they are in the current directory.
+ works in-place
+ :param args: parser args
+ """
- ctx = self.ctx
- dist = self._dist
-
- # Manually fixing these arguments at the string stage is
- # unsatisfactory and should probably be changed somehow, but
- # we can't leave it until later as the build.py scripts assume
- # they are in the current directory.
fix_args = ('--dir', '--private', '--add-jar', '--add-source',
- '--whitelist', '--blacklist', '--presplash', '--icon')
+ '--whitelist', '--blacklist', '--presplash', '--icon',
+ '--icon-bg', '--icon-fg')
unknown_args = args.unknown_args
+
+ for asset in args.assets:
+ if ":" in asset:
+ asset_src, asset_dest = asset.split(":")
+ else:
+ asset_src = asset_dest = asset
+ # take abspath now, because build.py will be run in bootstrap dir
+ unknown_args += ["--asset", os.path.abspath(asset_src)+":"+asset_dest]
+ for resource in args.resources:
+ if ":" in resource:
+ resource_src, resource_dest = resource.split(":")
+ else:
+ resource_src = resource
+ resource_dest = ""
+ # take abspath now, because build.py will be run in bootstrap dir
+ unknown_args += ["--resource", os.path.abspath(resource_src)+":"+resource_dest]
for i, arg in enumerate(unknown_args):
argx = arg.split('=')
if argx[0] in fix_args:
@@ -795,6 +1021,12 @@ class ToolchainCL(object):
elif i + 1 < len(unknown_args):
unknown_args[i+1] = realpath(expanduser(unknown_args[i+1]))
+ @staticmethod
+ def _prepare_release_env(args):
+ """
+ prepares envitonment dict with the necessary flags for signing an apk
+ :param args: parser args
+ """
env = os.environ.copy()
if args.build_mode == 'release':
if args.keystore:
@@ -808,125 +1040,152 @@ class ToolchainCL(object):
elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env:
env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw
- build = imp.load_source('build', join(dist.dist_dir, 'build.py'))
+ return env
+
+ def _build_package(self, args, package_type):
+ """
+ Creates an android package using gradle
+ :param args: parser args
+ :param package_type: one of 'apk', 'aar', 'aab'
+ :return (gradle output, build_args)
+ """
+ ctx = self.ctx
+ dist = self._dist
+ bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
+ ctx.prepare_bootstrap(bs)
+ self._fix_args(args)
+ env = self._prepare_release_env(args)
+
with current_directory(dist.dist_dir):
self.hook("before_apk_build")
os.environ["ANDROID_API"] = str(self.ctx.android_api)
- build_args = build.parse_args(args.unknown_args)
+ build = load_source('build', join(dist.dist_dir, 'build.py'))
+ build_args = build.parse_args_and_make_package(
+ args.unknown_args
+ )
+
self.hook("after_apk_build")
self.hook("before_apk_assemble")
+ build_tools_versions = os.listdir(join(ctx.sdk_dir,
+ 'build-tools'))
+ build_tools_versions = sorted(build_tools_versions,
+ key=LooseVersion)
+ build_tools_version = build_tools_versions[-1]
+ info(('Detected highest available build tools '
+ 'version to be {}').format(build_tools_version))
- build_type = ctx.java_build_tool
- if build_type == 'auto':
- info('Selecting java build tool:')
+ if build_tools_version < '25.0':
+ raise BuildInterruptingException(
+ 'build_tools >= 25 is required, but %s is installed' % build_tools_version)
+ if not exists("gradlew"):
+ raise BuildInterruptingException("gradlew file is missing")
- build_tools_versions = os.listdir(join(ctx.sdk_dir,
- 'build-tools'))
- build_tools_versions.sort(key=LooseVersion)
- build_tools_version = build_tools_versions[-1]
- info(('Detected highest available build tools '
- 'version to be {}').format(build_tools_version))
+ env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir
+ env["ANDROID_HOME"] = self.ctx.sdk_dir
- if build_tools_version >= '25.0' and exists('gradlew'):
- build_type = 'gradle'
- info(' Building with gradle, as gradle executable is '
- 'present')
- else:
- build_type = 'ant'
- if build_tools_version < '25.0':
- info((' Building with ant, as the highest '
- 'build-tools-version is only {}').format(
- build_tools_version))
- else:
- info(' Building with ant, as no gradle executable '
- 'detected')
+ gradlew = sh.Command('./gradlew')
- if build_type == 'gradle':
- # gradle-based build
- env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir
- env["ANDROID_HOME"] = self.ctx.sdk_dir
-
- gradlew = sh.Command('./gradlew')
- if exists('/usr/bin/dos2unix'):
- # .../dists/bdisttest_python3/gradlew
- # .../build/bootstrap_builds/sdl2-python3crystax/gradlew
- # if docker on windows, gradle contains CRLF
- output = shprint(
- sh.Command('dos2unix'), gradlew._path.decode('utf8'),
- _tail=20, _critical=True, _env=env
+ if exists('/usr/bin/dos2unix'):
+ # .../dists/bdisttest_python3/gradlew
+ # .../build/bootstrap_builds/sdl2-python3/gradlew
+ # if docker on windows, gradle contains CRLF
+ output = shprint(
+ sh.Command('dos2unix'), gradlew._path.decode('utf8'),
+ _tail=20, _critical=True, _env=env
+ )
+ if args.build_mode == "debug":
+ if package_type == "aab":
+ raise BuildInterruptingException(
+ "aab is meant only for distribution and is not available in debug mode. "
+ "Instead, you can use apk while building for debugging purposes."
)
- if args.build_mode == "debug":
- gradle_task = "assembleDebug"
- elif args.build_mode == "release":
+ gradle_task = "assembleDebug"
+ elif args.build_mode == "release":
+ if package_type in ["apk", "aar"]:
gradle_task = "assembleRelease"
- else:
- raise BuildInterruptingException(
- "Unknown build mode {} for apk()".format(args.build_mode))
- output = shprint(gradlew, "--console=plain", gradle_task,
- "publishReleasePublicationToSonatypeRepository",
- _tail=20,
- _critical=True, _env=env)
-
- # gradle output apks somewhere else
- # and don't have version in file
- apk_dir = join(dist.dist_dir,
- "build", "outputs", "aar")
- apk_glob = "*-{}.aar"
- apk_add_version = True
-
+ elif package_type == "aab":
+ gradle_task = "bundleRelease"
else:
- # ant-based build
- try:
- ant = sh.Command('ant')
- except sh.CommandNotFound:
- raise BuildInterruptingException(
- 'Could not find ant binary, please install it '
- 'and make sure it is in your $PATH.')
- output = shprint(ant, args.build_mode, _tail=20,
- _critical=True, _env=env)
- apk_dir = join(dist.dist_dir, "bin")
- apk_glob = "*-*-{}.aar"
- apk_add_version = False
+ raise BuildInterruptingException(
+ "Unknown build mode {} for apk()".format(args.build_mode))
- self.hook("after_apk_assemble")
+ # WARNING: We should make sure to clean the build directory before building.
+ # See PR: kivy/python-for-android#2705
+ output = shprint(gradlew, "clean", gradle_task, '--stacktrace', _tail=20,
+ _critical=True, _env=env)
+ return output, build_args
+
+ def _finish_package(self, args, output, build_args, package_type, output_dir):
+ """
+ Finishes the package after the gradle script run
+ :param args: the parser args
+ :param output: RunningCommand output
+ :param build_args: build args as returned by build.parse_args
+ :param package_type: one of 'apk', 'aar', 'aab'
+ :param output_dir: where to put the package file
+ """
+
+ package_glob = "*-{}.%s" % package_type
+ package_add_version = True
+
+ self.hook("after_apk_assemble")
info_main('# Copying android package to current directory')
- apk_re = re.compile(r'.*Package: (.*\.aar)$')
- apk_file = None
+ package_re = re.compile(r'.*Package: (.*\.apk)$')
+ package_file = None
for line in reversed(output.splitlines()):
- m = apk_re.match(line)
+ m = package_re.match(line)
if m:
- apk_file = m.groups()[0]
+ package_file = m.groups()[0]
break
-
- if not apk_file:
- info_main('# AAR not found in build output. Guessing...')
+ if not package_file:
+ info_main('# Android package filename not found in build output. Guessing...')
if args.build_mode == "release":
suffixes = ("release", "release-unsigned")
else:
suffixes = ("debug", )
for suffix in suffixes:
- apks = glob.glob(join(apk_dir, apk_glob.format(suffix)))
- if apks:
- if len(apks) > 1:
- info('More than one built AAR found... guessing you '
- 'just built {}'.format(apks[-1]))
- apk_file = apks[-1]
+
+ package_files = glob.glob(join(output_dir, package_glob.format(suffix)))
+ if package_files:
+ if len(package_files) > 1:
+ info('More than one built APK found... guessing you '
+ 'just built {}'.format(package_files[-1]))
+ package_file = package_files[-1]
break
else:
- raise BuildInterruptingException('Couldn\'t find the built AAR')
+ raise BuildInterruptingException('Couldn\'t find the built APK')
- info_main('# Found AAR file: {}'.format(apk_file))
- if apk_add_version:
- info('# Add version number to AAR')
- apk_name, apk_suffix = basename(apk_file).split("-", 1)
- apk_file_dest = "{}-{}-{}".format(
- apk_name, build_args.version, apk_suffix)
- info('# AAR renamed to {}'.format(apk_file_dest))
- shprint(sh.cp, apk_file, apk_file_dest)
+ info_main('# Found android package file: {}'.format(package_file))
+ package_extension = f".{package_type}"
+ if package_add_version:
+ info('# Add version number to android package')
+ package_name = basename(package_file)[:-len(package_extension)]
+ package_file_dest = "{}-{}{}".format(
+ package_name, build_args.version, package_extension)
+ info('# Android package renamed to {}'.format(package_file_dest))
+ shprint(sh.cp, package_file, package_file_dest)
else:
- shprint(sh.cp, apk_file, './')
+ shprint(sh.cp, package_file, './')
+
+ @require_prebuilt_dist
+ def apk(self, args):
+ output, build_args = self._build_package(args, package_type='apk')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'apk', args.build_mode)
+ self._finish_package(args, output, build_args, 'apk', output_dir)
+
+ @require_prebuilt_dist
+ def aar(self, args):
+ output, build_args = self._build_package(args, package_type='aar')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'aar')
+ self._finish_package(args, output, build_args, 'aar', output_dir)
+
+ @require_prebuilt_dist
+ def aab(self, args):
+ output, build_args = self._build_package(args, package_type='aab')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'bundle', args.build_mode)
+ self._finish_package(args, output, build_args, 'aab', output_dir)
@require_prebuilt_dist
def create(self, args):
@@ -954,7 +1213,7 @@ class ToolchainCL(object):
if dists:
print('{Style.BRIGHT}Distributions currently installed are:'
- '{Style.RESET_ALL}'.format(Style=Out_Style, Fore=Out_Fore))
+ '{Style.RESET_ALL}'.format(Style=Out_Style))
pretty_log_dists(dists, print)
else:
print('{Style.BRIGHT}There are no dists currently built.'
@@ -1017,6 +1276,9 @@ class ToolchainCL(object):
sys.stdout.write(line)
sys.stdout.flush()
+ def recommendations(self, args):
+ print_recommendations()
+
def build_status(self, _args):
"""Print the status of the specified build. """
print('{Style.BRIGHT}Bootstraps whose core components are probably '
@@ -1046,12 +1308,5 @@ class ToolchainCL(object):
print(recipe_str)
-def main():
- try:
- ToolchainCL()
- except BuildInterruptingException as exc:
- handle_build_exception(exc)
-
-
if __name__ == "__main__":
main()
diff --git a/p4a/pythonforandroid/tools/biglink b/p4a/pythonforandroid/tools/biglink
index 6b86dbf..8a8e561 100755
--- a/p4a/pythonforandroid/tools/biglink
+++ b/p4a/pythonforandroid/tools/biglink
@@ -1,11 +1,10 @@
#!/usr/bin/env python
-from __future__ import print_function
import os
import sys
import subprocess
-sofiles = [ ]
+sofiles = []
for directory in sys.argv[2:]:
@@ -20,7 +19,7 @@ for directory in sys.argv[2:]:
sofiles.append(fn[:-2])
# The raw argument list.
-args = [ ]
+args = []
for fn in sofiles:
afn = fn + ".o"
@@ -31,7 +30,7 @@ for fn in sofiles:
data = fd.read()
args.extend(data.split(" "))
-unique_args = [ ]
+unique_args = []
while args:
a = args.pop()
if a in ('-L', ):
diff --git a/p4a/pythonforandroid/tools/liblink b/p4a/pythonforandroid/tools/liblink
index 523eef9..de837e6 100755
--- a/p4a/pythonforandroid/tools/liblink
+++ b/p4a/pythonforandroid/tools/liblink
@@ -1,6 +1,5 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
-from __future__ import print_function
import sys
import subprocess
from os import environ
@@ -22,7 +21,7 @@ while i < len(sys.argv):
i += 1
continue
- if opt.startswith("-l") or opt.startswith("-L"):
+ if opt.startswith(("-l", "-L")):
libs.append(opt)
continue
@@ -34,27 +33,8 @@ while i < len(sys.argv):
i += 1
continue
- if opt.startswith("-I"):
- continue
-
- if opt.startswith("-m"):
- continue
-
- if opt.startswith("-f"):
- continue
-
- if opt.startswith("-O"):
- continue
-
- if opt.startswith("-g"):
- continue
-
- if opt.startswith("-D"):
- continue
-
- if opt.startswith("-R"):
- # for -rpath, not implemented yet.
- continue
+ if opt.startswith(
+ ("-I", "-isystem", "-m", "-f", "-O", "-g", "-D", "-R")):
if opt.startswith("-"):
print(sys.argv)
diff --git a/p4a/pythonforandroid/util.py b/p4a/pythonforandroid/util.py
index 9c007c2..f290cdc 100644
--- a/p4a/pythonforandroid/util.py
+++ b/p4a/pythonforandroid/util.py
@@ -1,29 +1,11 @@
import contextlib
from os.path import exists, join
from os import getcwd, chdir, makedirs, walk, uname
-import io
-import json
-import sh
import shutil
-import sys
from fnmatch import fnmatch
from tempfile import mkdtemp
-try:
- from urllib.request import FancyURLopener
-except ImportError:
- from urllib import FancyURLopener
-
from pythonforandroid.logger import (logger, Err_Fore, error, info)
-IS_PY3 = sys.version_info[0] >= 3
-
-
-class WgetDownloader(FancyURLopener):
- version = ('Wget/1.17.1')
-
-
-urlretrieve = WgetDownloader().retrieve
-
build_platform = '{system}-{machine}'.format(
system=uname()[0], machine=uname()[-1]).lower()
@@ -62,90 +44,6 @@ def ensure_dir(filename):
makedirs(filename)
-class JsonStore(object):
- """Replacement of shelve using json, needed for support python 2 and 3.
- """
-
- def __init__(self, filename):
- super(JsonStore, self).__init__()
- self.filename = filename
- self.data = {}
- if exists(filename):
- try:
- with io.open(filename, encoding='utf-8') as fd:
- self.data = json.load(fd)
- except ValueError:
- print("Unable to read the state.db, content will be replaced.")
-
- def __getitem__(self, key):
- return self.data[key]
-
- def __setitem__(self, key, value):
- self.data[key] = value
- self.sync()
-
- def __delitem__(self, key):
- del self.data[key]
- self.sync()
-
- def __contains__(self, item):
- return item in self.data
-
- def get(self, item, default=None):
- return self.data.get(item, default)
-
- def keys(self):
- return self.data.keys()
-
- def remove_all(self, prefix):
- for key in self.data.keys()[:]:
- if not key.startswith(prefix):
- continue
- del self.data[key]
- self.sync()
-
- def sync(self):
- # http://stackoverflow.com/questions/12309269/write-json-data-to-file-in-python/14870531#14870531
- if IS_PY3:
- with open(self.filename, 'w') as fd:
- json.dump(self.data, fd, ensure_ascii=False)
- else:
- with io.open(self.filename, 'w', encoding='utf-8') as fd:
- fd.write(unicode(json.dumps(self.data, ensure_ascii=False))) # noqa F821
-
-
-def which(program, path_env):
- '''Locate an executable in the system.'''
- import os
-
- def is_exe(fpath):
- return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
-
- fpath, fname = os.path.split(program)
- if fpath:
- if is_exe(program):
- return program
- else:
- for path in path_env.split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if is_exe(exe_file):
- return exe_file
-
- return None
-
-
-def get_virtualenv_executable():
- 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')
- return virtualenv
-
-
def walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns):
"""Recursively walks all the files and directories in ``dirn``,
ignoring directories that match any pattern in ``invalid_dirns``
@@ -176,9 +74,23 @@ def walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns):
yield join(dirn, filen)
+def load_source(module, filename):
+ # Python 3.5+
+ import importlib.util
+ if hasattr(importlib.util, 'module_from_spec'):
+ spec = importlib.util.spec_from_file_location(module, filename)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod
+ else:
+ # Python 3.3 and 3.4:
+ from importlib.machinery import SourceFileLoader
+ return SourceFileLoader(module, filename).load_module()
+
+
class BuildInterruptingException(Exception):
def __init__(self, message, instructions=None):
- super(BuildInterruptingException, self).__init__(message, instructions)
+ super().__init__(message, instructions)
self.message = message
self.instructions = instructions
diff --git a/p4a/setup.py b/p4a/setup.py
index 4db62a5..1910be6 100644
--- a/p4a/setup.py
+++ b/p4a/setup.py
@@ -15,15 +15,15 @@ package_data = {'': ['*.tmpl',
data_files = []
-
-if os.name == 'nt':
- install_reqs = ['appdirs', 'colorama>=0.3.3', 'jinja2',
- 'six']
-else:
+#
+# if os.name == 'nt':
+# install_reqs = ['appdirs', 'colorama>=0.3.3', 'jinja2',
+# 'six']
+# else:
# don't use sh after 1.12.5, we have performance issues
# https://github.com/amoffat/sh/issues/378
- install_reqs = ['appdirs', 'colorama>=0.3.3', 'sh>=1.10,<1.12.5', 'jinja2',
- 'six']
+install_reqs = ['appdirs', 'colorama>=0.3.3', 'sh>=1.10,<1.12.5', 'jinja2',
+ 'six', 'enum34']
# By specifying every file manually, package_data will be able to
# include them in binary distributions. Note that we have to add
@@ -54,13 +54,17 @@ recursively_include(package_data, 'pythonforandroid/bootstraps/webview',
recursively_include(package_data, 'pythonforandroid',
['liblink', 'biglink', 'liblink.sh'])
-with open(join(dirname(__file__), 'README.rst')) as fileh:
+with open(join(dirname(__file__), 'README.md',
+ encoding="utf-8",
+ errors="replace")) as fileh:
long_description = fileh.read()
init_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')
version = None
try:
- with open(init_filen) as fileh:
+ with open(init_filen,
+ encoding="utf-8",
+ errors="replace") as fileh:
lines = fileh.readlines()
except IOError:
pass
@@ -86,15 +90,17 @@ setup(name='python-for-android',
install_requires=install_reqs,
entry_points={
'console_scripts': [
- 'python-for-android = pythonforandroid.toolchain:main',
- 'p4a = pythonforandroid.toolchain:main',
- ],
+ 'python-for-android = pythonforandroid.entrypoints:main',
+ 'p4a = pythonforandroid.entrypoints:main',
+ ],
'distutils.commands': [
'apk = pythonforandroid.bdistapk:BdistAPK',
- ],
- },
- classifiers = [
- 'Development Status :: 4 - Beta',
+ 'aar = pythonforandroid.bdistapk:BdistAAR',
+ 'aab = pythonforandroid.bdistapk:BdistAAB',
+ ],
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
@@ -103,11 +109,14 @@ setup(name='python-for-android',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Android',
'Programming Language :: C',
- 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
'Topic :: Software Development',
'Topic :: Utilities',
- ],
+ ],
packages=packages,
package_data=package_data,
)
diff --git a/recipes/android/__init__.py b/recipes/android/__init__.py
index 1611095..d290a1a 100644
--- a/recipes/android/__init__.py
+++ b/recipes/android/__init__.py
@@ -1,6 +1,5 @@
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
from pythonforandroid.util import current_directory
-from pythonforandroid.patching import will_build
from pythonforandroid import logger
from os.path import join
@@ -13,154 +12,78 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
src_filename = 'src'
- depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
+ depends = [('sdl2', 'genericndkbuild'), 'pyjnius']
config_env = {}
def get_recipe_env(self, arch):
- env = super(AndroidRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env.update(self.config_env)
return env
def prebuild_arch(self, arch):
- super(AndroidRecipe, self).prebuild_arch(arch)
+ super().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', 'sdl2_gradle')
- is_pygame = bootstrap_name in ('pygame',)
- is_webview = bootstrap_name in ('webview',)
+ # 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_lbry = bootstrap_name in ('lbry',)
-
- if is_sdl2 or is_webview or is_lbry:
- if is_sdl2:
- bootstrap = 'sdl2'
- java_ns = 'org.kivy.android'
- jni_ns = 'org/kivy/android'
- elif is_pygame:
- java_ns = 'org.renpy.android'
- jni_ns = 'org/renpy/android'
+ is_sdl2 = (bootstrap_name == "sdl2")
+ if bootstrap_name in ["sdl2", "webview", "service_only", "service_library", "lbry"]:
+ java_ns = u'org.kivy.android'
+ jni_ns = u'org/kivy/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 = {
'BOOTSTRAP': bootstrap,
'IS_SDL2': int(is_sdl2),
- 'IS_PYGAME': int(is_pygame),
- 'PY2': int(will_build('python2')(self)),
+ 'PY2': 0,
'JAVA_NAMESPACE': java_ns,
'JNI_NAMESPACE': jni_ns,
+ 'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name,
+ 'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'),
+ 'SERVICE_CLASS_NAME': self.ctx.service_class_name,
}
- 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)
-recipe = AndroidRecipe()
-
-
-'''
-from pythonforandroid.recipe import CythonRecipe, Recipe, IncludedFilesBehaviour
-from pythonforandroid.util import current_directory
-from pythonforandroid.patching import will_build
-from pythonforandroid import logger
-
-from os.path import join
-
-
-class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
- # name = 'android'
- version = None
- url = None
-
- src_filename = 'src'
-
- depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
-
- call_hostpython_via_targetpython = False
-
- config_env = {}
-
- def get_recipe_env(self, arch):
- env = super(AndroidRecipe, self).get_recipe_env(arch)
- env.update(self.config_env)
-
- target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = join(target_python, 'python-install')
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + ' -lpython2.7'
-
- return env
-
- def prebuild_arch(self, arch):
- super(AndroidRecipe, self).prebuild_arch(arch)
-
- tpxi = 'DEF {} = {}\n'
- th = '#define {} {}\n'
- tpy = '{} = {}\n'
-
- bootstrap = bootstrap_name = self.ctx.bootstrap.name
- is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
- is_pygame = bootstrap_name in ('pygame',)
- is_webview = bootstrap_name in ('webview')
- is_lbry = bootstrap_name in ('lbry')
-
- if is_sdl2 or is_webview or is_lbry:
if is_sdl2:
- bootstrap = 'sdl2'
- java_ns = 'org.kivy.android'
- jni_ns = 'org/kivy/android'
- elif is_pygame:
- java_ns = 'org.renpy.android'
- jni_ns = 'org/renpy/android'
- else:
- logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
- exit(1)
-
- config = {
- 'BOOTSTRAP': bootstrap,
- 'IS_SDL2': int(is_sdl2),
- 'IS_PYGAME': int(is_pygame),
- 'PY2': int(will_build('python2')(self)),
- 'JAVA_NAMESPACE': java_ns,
- '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)
-
- 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')
+ fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
+ )
+ else:
+ fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv WebView_AndroidGetJNIEnv\n'
+ )
recipe = AndroidRecipe()
-'''
\ No newline at end of file
diff --git a/recipes/android/src/android/__init__.py b/recipes/android/src/android/__init__.py
index c50c761..cb95734 100644
--- a/recipes/android/src/android/__init__.py
+++ b/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/recipes/android/src/android/_android.pyx b/recipes/android/src/android/_android.pyx
index f4f37d8..6708b84 100644
--- a/recipes/android/src/android/_android.pyx
+++ b/recipes/android/src/android/_android.pyx
@@ -2,22 +2,6 @@
include "config.pxi"
-IF BOOTSTRAP == 'pygame':
- cdef extern int SDL_ANDROID_CheckPause()
- cdef extern void SDL_ANDROID_WaitForResume() nogil
- cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
-
- def check_pause():
- return SDL_ANDROID_CheckPause()
-
- def wait_for_resume():
- android_accelerometer_enable(False)
- SDL_ANDROID_WaitForResume()
- android_accelerometer_enable(accelerometer_enabled)
-
- def map_key(scancode, keysym):
- SDL_ANDROID_MapKey(scancode, keysym)
-
# Android keycodes.
KEYCODE_UNKNOWN = 0
KEYCODE_SOFT_LEFT = 1
@@ -175,13 +159,11 @@ 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(ACTIVITY_CLASS_NAME)
+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():
@@ -274,42 +256,6 @@ def get_buildinfo():
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
return binfo
-IF IS_PYGAME:
- # Activate input - required to receive input events.
- cdef extern void android_activate_input()
-
- def init():
- android_activate_input()
-
- # Action send
- cdef extern void android_action_send(char*, char*, char*, char*, char*)
- def action_send(mimetype, filename=None, subject=None, text=None,
- chooser_title=None):
- cdef char *j_mimetype = mimetype
- cdef char *j_filename = NULL
- cdef char *j_subject = NULL
- cdef char *j_text = NULL
- cdef char *j_chooser_title = NULL
- if filename is not None:
- j_filename = filename
- if subject is not None:
- j_subject = subject
- if text is not None:
- j_text = text
- if chooser_title is not None:
- j_chooser_title = chooser_title
- android_action_send(j_mimetype, j_filename, j_subject, j_text,
- j_chooser_title)
-
- cdef extern int android_checkstop()
- cdef extern void android_ackstop()
-
- def check_stop():
- return android_checkstop()
-
- def ack_stop():
- android_ackstop()
-
# -------------------------------------------------------------------
# URL Opening.
def open_url(url):
@@ -332,19 +278,31 @@ class AndroidBrowser(object):
return open_url(url)
import webbrowser
-webbrowser.register('android', AndroidBrowser, None, -1)
+webbrowser.register('android', AndroidBrowser)
+
+
+def start_service(title="Background Service",
+ description="", arg="",
+ as_foreground=True):
+ # Legacy None value support (for old function signature style):
+ if title is None:
+ title = "Background Service"
+ if description is None:
+ description = ""
+ if arg is None:
+ arg = ""
+
+ # Start service:
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if as_foreground:
+ mActivity.start_service(
+ title, description, arg
+ )
+ else:
+ mActivity.start_service_not_as_foreground(
+ title, description, arg
+ )
-cdef extern void android_start_service(char *, char *, char *)
-def start_service(title=None, description=None, arg=None):
- cdef char *j_title = NULL
- cdef char *j_description = NULL
- if title is not None:
- j_title = title
- if description is not None:
- j_description = description
- if arg is not None:
- j_arg = arg
- android_start_service(j_title, j_description, j_arg)
cdef extern void android_stop_service()
def stop_service():
diff --git a/recipes/android/src/android/_android_billing.pyx b/recipes/android/src/android/_android_billing.pyx
index bd6bb2e..d5ed2a0 100644
--- a/recipes/android/src/android/_android_billing.pyx
+++ b/recipes/android/src/android/_android_billing.pyx
@@ -15,7 +15,7 @@ class BillingService(object):
BILLING_TYPE_SUBSCRIPTION = 'subs'
def __init__(self, callback):
- super(BillingService, self).__init__()
+ super().__init__()
self.callback = callback
self.purchased_items = None
android_billing_service_start()
diff --git a/recipes/android/src/android/_android_jni.c b/recipes/android/src/android/_android_jni.c
index 8eee770..cf1b1bf 100644
--- a/recipes/android/src/android/_android_jni.c
+++ b/recipes/android/src/android/_android_jni.c
@@ -201,146 +201,6 @@ void android_get_buildinfo() {
}
}
-#if IS_PYGAME
-void android_activate_input(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
- aassert(mid);
- }
-
- (*env)->CallStaticVoidMethod(env, cls, mid);
-}
-
-int android_checkstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
- aassert(mid);
- }
-
- return (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_ackstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
- aassert(mid);
- }
-
- (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/Action");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "send",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
- jstring j_filename = NULL;
- jstring j_subject = NULL;
- jstring j_text = NULL;
- jstring j_chooser_title = NULL;
- if ( filename != NULL )
- j_filename = (*env)->NewStringUTF(env, filename);
- if ( subject != NULL )
- j_subject = (*env)->NewStringUTF(env, subject);
- if ( text != NULL )
- j_text = (*env)->NewStringUTF(env, text);
- if ( chooser_title != NULL )
- j_chooser_title = (*env)->NewStringUTF(env, text);
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- j_mimeType, j_filename, j_subject, j_text,
- j_chooser_title);
-}
-
-void android_open_url(char *url) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
- aassert(mid);
- }
-
- PUSH_FRAME;
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- (*env)->NewStringUTF(env, url)
- );
-
- POP_FRAME;
-}
-#endif // IS_PYGAME
-
-void android_start_service(char *title, char *description, char *arg) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "start_service",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_title = NULL;
- jstring j_description = NULL;
- jstring j_arg = NULL;
- if ( title != 0 )
- j_title = (*env)->NewStringUTF(env, title);
- if ( description != 0 )
- j_description = (*env)->NewStringUTF(env, description);
- if ( arg != 0 )
- j_arg = (*env)->NewStringUTF(env, arg);
-
- (*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
-}
-
void android_stop_service() {
static JNIEnv *env = NULL;
static jclass *cls = NULL;
diff --git a/recipes/android/src/android/_ctypes_library_finder.py b/recipes/android/src/android/_ctypes_library_finder.py
new file mode 100644
index 0000000..a03512e
--- /dev/null
+++ b/recipes/android/src/android/_ctypes_library_finder.py
@@ -0,0 +1,67 @@
+
+import sys
+import os
+
+
+def get_activity_lib_dir(activity_name):
+ from jnius import autoclass
+
+ # Get the actual activity instance:
+ activity_class = autoclass(activity_name)
+ if activity_class is None:
+ return None
+ activity = None
+ if hasattr(activity_class, "mActivity") and \
+ activity_class.mActivity is not None:
+ activity = activity_class.mActivity
+ elif hasattr(activity_class, "mService") and \
+ activity_class.mService is not None:
+ activity = activity_class.mService
+ if activity is None:
+ return None
+
+ # Extract the native lib dir from the activity instance:
+ package_name = activity.getApplicationContext().getPackageName()
+ manager = activity.getApplicationContext().getPackageManager()
+ manager_class = autoclass("android.content.pm.PackageManager")
+ native_lib_dir = manager.getApplicationInfo(
+ package_name, manager_class.GET_SHARED_LIBRARY_FILES
+ ).nativeLibraryDir
+ return native_lib_dir
+
+
+def does_libname_match_filename(search_name, file_path):
+ # Filter file names so given search_name="mymodule" we match one of:
+ # mymodule.so (direct name + .so)
+ # libmymodule.so (added lib prefix)
+ # mymodule.arm64.so (added dot-separated middle parts)
+ # mymodule.so.1.3.4 (added dot-separated version tail)
+ # and all above (all possible combinations)
+ import re
+ file_name = os.path.basename(file_path)
+ return (re.match(r"^(lib)?" + re.escape(search_name) +
+ r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)
+
+
+def find_library(name):
+ # Obtain all places for native libraries:
+ if sys.maxsize > 2**32: # 64bit-build
+ lib_search_dirs = ["/system/lib64", "/system/lib"]
+ else:
+ lib_search_dirs = ["/system/lib"]
+ lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
+ if lib_dir_1 is not None:
+ lib_search_dirs.insert(0, lib_dir_1)
+ lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
+ if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
+ lib_search_dirs.insert(0, lib_dir_2)
+
+ # Now scan the lib dirs:
+ for lib_dir in [ldir for ldir in lib_search_dirs if os.path.exists(ldir)]:
+ filelist = [
+ f for f in os.listdir(lib_dir)
+ if does_libname_match_filename(name, f)
+ ]
+ if len(filelist) > 0:
+ return os.path.join(lib_dir, filelist[0])
+ return None
diff --git a/recipes/android/src/android/activity.py b/recipes/android/src/android/activity.py
index 94e08e7..78d068c 100644
--- a/recipes/android/src/android/activity.py
+++ b/recipes/android/src/android/activity.py
@@ -1,18 +1,20 @@
-from jnius import PythonJavaClass, java_method, autoclass, cast
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from jnius import PythonJavaClass, autoclass, java_method
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
-_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
+_activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
_callbacks = {
'on_new_intent': [],
- 'on_activity_result': [] }
+ 'on_activity_result': [],
+}
+
class NewIntentListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']
__javacontext__ = 'app'
def __init__(self, callback, **kwargs):
- super(NewIntentListener, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.callback = callback
@java_method('(Landroid/content/Intent;)V')
@@ -21,11 +23,11 @@ class NewIntentListener(PythonJavaClass):
class ActivityResultListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']
__javacontext__ = 'app'
def __init__(self, callback):
- super(ActivityResultListener, self).__init__()
+ super().__init__()
self.callback = callback
@java_method('(IILandroid/content/Intent;)V')
@@ -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:
@@ -59,3 +62,153 @@ def unbind(**kwargs):
elif event == 'on_activity_result':
_activity.unregisterActivityResultListener(listener)
+
+# Keep a reference to all the registered classes so that python doesn't
+# garbage collect them.
+_lifecycle_callbacks = set()
+
+
+class ActivityLifecycleCallbacks(PythonJavaClass):
+ """Callback class for handling PythonActivity lifecycle transitions"""
+
+ __javainterfaces__ = ['android/app/Application$ActivityLifecycleCallbacks']
+
+ def __init__(self, callbacks):
+ super().__init__()
+
+ # It would be nice to use keyword arguments, but PythonJavaClass
+ # doesn't allow that in its __cinit__ method.
+ if not isinstance(callbacks, dict):
+ raise ValueError('callbacks must be a dict instance')
+ self.callbacks = callbacks
+
+ def _callback(self, name, *args):
+ func = self.callbacks.get(name)
+ if func:
+ return func(*args)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityCreated(self, activity, savedInstanceState):
+ self._callback('onActivityCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityDestroyed(self, activity):
+ self._callback('onActivityDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPaused(self, activity):
+ self._callback('onActivityPaused', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPostCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostDestroyed(self, activity):
+ self._callback('onActivityPostDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostPaused(self, activity):
+ self._callback('onActivityPostPaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostResumed(self, activity):
+ self._callback('onActivityPostResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPostSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStarted(self, activity):
+ self._callback('onActivityPostStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStopped(self, activity):
+ self._callback('onActivityPostStopped', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPreCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreDestroyed(self, activity):
+ self._callback('onActivityPreDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPrePaused(self, activity):
+ self._callback('onActivityPrePaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreResumed(self, activity):
+ self._callback('onActivityPreResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPreSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStarted(self, activity):
+ self._callback('onActivityPreStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStopped(self, activity):
+ self._callback('onActivityPreStopped', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityResumed(self, activity):
+ self._callback('onActivityResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivitySaveInstanceState(self, activity, outState):
+ self._callback('onActivitySaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStarted(self, activity):
+ self._callback('onActivityStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStopped(self, activity):
+ self._callback('onActivityStopped', activity)
+
+
+def register_activity_lifecycle_callbacks(**callbacks):
+ """Register ActivityLifecycleCallbacks instance
+
+ The callbacks are supplied as keyword arguments corresponding to the
+ Application.ActivityLifecycleCallbacks methods such as
+ onActivityStarted. See the ActivityLifecycleCallbacks documentation
+ for the signature of each method.
+
+ The ActivityLifecycleCallbacks instance is returned so it can be
+ supplied to unregister_activity_lifecycle_callbacks if needed.
+ """
+ instance = ActivityLifecycleCallbacks(callbacks)
+ _lifecycle_callbacks.add(instance)
+
+ # Use the registerActivityLifecycleCallbacks method from the
+ # Activity class if it's available (API 29) since it guarantees the
+ # callbacks will only be run for that activity. Otherwise, fallback
+ # to the method on the Application class (API 14). In practice there
+ # should be no difference since p4a applications only have a single
+ # activity.
+ if hasattr(_activity, 'registerActivityLifecycleCallbacks'):
+ _activity.registerActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.registerActivityLifecycleCallbacks(instance)
+ return instance
+
+
+def unregister_activity_lifecycle_callbacks(instance):
+ """Unregister ActivityLifecycleCallbacks instance"""
+ if hasattr(_activity, 'unregisterActivityLifecycleCallbacks'):
+ _activity.unregisterActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.unregisterActivityLifecycleCallbacks(instance)
+
+ try:
+ _lifecycle_callbacks.remove(instance)
+ except KeyError:
+ pass
diff --git a/recipes/android/src/android/billing.py b/recipes/android/src/android/billing.py
index 46715dc..0ea1008 100644
--- a/recipes/android/src/android/billing.py
+++ b/recipes/android/src/android/billing.py
@@ -3,5 +3,3 @@ Android Billing API
===================
'''
-
-from android._android_billing import *
diff --git a/recipes/android/src/android/broadcast.py b/recipes/android/src/android/broadcast.py
index ba3dfc9..3232d83 100644
--- a/recipes/android/src/android/broadcast.py
+++ b/recipes/android/src/android/broadcast.py
@@ -2,7 +2,7 @@
# Broadcast receiver bridge
from jnius import autoclass, PythonJavaClass, java_method
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
class BroadcastReceiver(object):
@@ -20,7 +20,7 @@ class BroadcastReceiver(object):
self.callback(context, intent)
def __init__(self, callback, actions=None, categories=None):
- super(BroadcastReceiver, self).__init__()
+ super().__init__()
self.callback = callback
if not actions and not categories:
@@ -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)
@@ -72,8 +72,7 @@ class BroadcastReceiver(object):
def context(self):
from os import environ
if 'PYTHON_SERVICE_ARGUMENT' in environ:
- PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
+ PythonService = autoclass(SERVICE_CLASS_NAME)
return PythonService.mService
- PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
return PythonActivity.mActivity
-
diff --git a/recipes/android/src/android/loadingscreen.py b/recipes/android/src/android/loadingscreen.py
new file mode 100644
index 0000000..a18162e
--- /dev/null
+++ b/recipes/android/src/android/loadingscreen.py
@@ -0,0 +1,9 @@
+
+from jnius import autoclass
+
+from android.config import ACTIVITY_CLASS_NAME
+
+
+def hide_loading_screen():
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.removeLoadingScreen()
diff --git a/recipes/android/src/android/mixer.py b/recipes/android/src/android/mixer.py
index 65e106d..303a953 100644
--- a/recipes/android/src/android/mixer.py
+++ b/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,7 +93,10 @@ def find_channel(force=False):
if not force:
return None
- return min(busy, key=lambda x : x.play_time)
+ busy.sort(key=lambda x: x.play_time)
+
+ return busy[0]
+
class ChannelImpl(object):
@@ -99,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
@@ -179,7 +195,8 @@ def Channel(n):
sound_serial = 0
-sounds = { }
+sounds = {}
+
class Sound(object):
@@ -194,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
@@ -212,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:
@@ -242,9 +258,11 @@ class Sound(object):
def get_length(self):
return 1.0
+
music_channel = Channel(256)
music_sound = None
+
class music(object):
@staticmethod
@@ -304,6 +322,3 @@ class music(object):
@staticmethod
def queue(filename):
return music_channel.queue(Sound(filename))
-
-
-
diff --git a/recipes/android/src/android/permissions.py b/recipes/android/src/android/permissions.py
new file mode 100644
index 0000000..0ce568f
--- /dev/null
+++ b/recipes/android/src/android/permissions.py
@@ -0,0 +1,618 @@
+import threading
+
+try:
+ from jnius import autoclass, PythonJavaClass, java_method
+except ImportError:
+ # To allow importing by build/manifest-creating code without
+ # pyjnius being present:
+ def autoclass(item):
+ raise RuntimeError("pyjnius not available")
+
+
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
+
+
+class Permission:
+ ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
+ ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
+ ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
+ ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_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_ADVERTISE = (
+ "android.permission.BLUETOOTH_ADVERTISE"
+ )
+ BLUETOOTH_CONNECT = (
+ "android.permission.BLUETOOTH_CONNECT"
+ )
+ BLUETOOTH_SCAN = (
+ "android.permission.BLUETOOTH_SCAN"
+ )
+ 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"
+ )
+ NEARBY_WIFI_DEVICES = (
+ "android.permission.NEARBY_WIFI_DEVICES"
+ )
+ 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"
+ )
+ POST_NOTIFICATIONS = (
+ "android.permission.POST_NOTIFICATIONS"
+ )
+ 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_MEDIA_AUDIO = (
+ "android.permission.READ_MEDIA_AUDIO"
+ )
+ READ_MEDIA_IMAGES = (
+ "android.permission.READ_MEDIA_IMAGES"
+ )
+ READ_MEDIA_VIDEO = (
+ "android.permission.READ_MEDIA_VIDEO"
+ )
+ 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"
+ )
+
+
+PERMISSION_GRANTED = 0
+PERMISSION_DENIED = -1
+
+
+class _onRequestPermissionsCallback(PythonJavaClass):
+ """Callback class for registering a Python callback from
+ onRequestPermissionsResult in PythonActivity.
+ """
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$PermissionsCallback']
+ __javacontext__ = 'app'
+
+ def __init__(self, func):
+ self.func = func
+ super().__init__()
+
+ @java_method('(I[Ljava/lang/String;[I)V')
+ def onRequestPermissionsResult(self, requestCode,
+ permissions, grantResults):
+ self.func(requestCode, permissions, grantResults)
+
+
+class _RequestPermissionsManager:
+ """Internal class for requesting Android permissions.
+
+ Permissions are requested through the method 'request_permissions' which
+ accepts a list of permissions and an optional callback.
+
+ Any callback will asynchronously receive arguments from
+ onRequestPermissionsResult on PythonActivity after requestPermissions is
+ called.
+
+ The callback supplied must accept two arguments: 'permissions' and
+ 'grantResults' (as supplied to onPermissionsCallbackResult).
+
+ Note that for SDK_INT < 23, run-time permissions are not required, and so
+ the callback will be called immediately.
+
+ The attribute '_java_callback' is initially None, but is set when the first
+ permissions request is made. It is set to an instance of
+ onRequestPermissionsCallback, which allows the Java callback to be
+ propagated to the class method 'python_callback'. This is then, in turn,
+ used to call an application callback if provided to request_permissions.
+
+ The attribute '_callback_id' is incremented with each call to
+ request_permissions which has a callback (the value '1' is used for any
+ call which does not pass a callback). This is passed to requestCode in
+ the Java call, and used to identify (via the _callbacks dictionary)
+ the matching call.
+ """
+ _SDK_INT = None
+ _java_callback = None
+ _callbacks = {1: None}
+ _callback_id = 1
+ # Lock to prevent multiple calls to request_permissions being handled
+ # simultaneously (as incrementing _callback_id is not atomic)
+ _lock = threading.Lock()
+
+ @classmethod
+ def register_callback(cls):
+ """Register Java callback for requestPermissions."""
+ cls._java_callback = _onRequestPermissionsCallback(cls.python_callback)
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.addPermissionsCallback(cls._java_callback)
+
+ @classmethod
+ def request_permissions(cls, permissions, callback=None):
+ """Requests Android permissions from PythonActivity.
+ If 'callback' is supplied, the request is made with a new requestCode
+ and the callback is stored in the _callbacks dict. When a Java callback
+ with the matching requestCode is received, callback will be called
+ with arguments of 'permissions' and 'grant_results'.
+ """
+ if not cls._SDK_INT:
+ # Get the Android build version and store it
+ VERSION = autoclass('android.os.Build$VERSION')
+ cls.SDK_INT = VERSION.SDK_INT
+ if cls.SDK_INT < 23:
+ # No run-time permissions needed, return immediately.
+ if callback:
+ callback(permissions, [True for x in permissions])
+ return
+ # Request permissions
+ with cls._lock:
+ if not cls._java_callback:
+ cls.register_callback()
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if not callback:
+ mActivity.requestPermissions(permissions)
+ else:
+ cls._callback_id += 1
+ mActivity.requestPermissionsWithRequestCode(
+ permissions, cls._callback_id)
+ cls._callbacks[cls._callback_id] = callback
+
+ @classmethod
+ def python_callback(cls, requestCode, permissions, grantResults):
+ """Calls the relevant callback with arguments of 'permissions'
+ and 'grantResults'."""
+ # Convert from Android codes to True/False
+ grant_results = [x == PERMISSION_GRANTED for x in grantResults]
+ if cls._callbacks.get(requestCode):
+ cls._callbacks[requestCode](permissions, grant_results)
+
+
+# Public API methods for requesting permissions
+
+def request_permissions(permissions, callback=None):
+ """Requests Android permissions.
+
+ Args:
+ permissions (str): A list of permissions to requests (str)
+ callback (callable, optional): A function to call when the request
+ is completed (callable)
+
+ Returns:
+ None
+
+ Notes:
+
+ Permission strings can be imported from the 'Permission' class in this
+ module. For example:
+
+ from android import Permission
+ permissions_list = [Permission.CAMERA,
+ Permission.WRITE_EXTERNAL_STORAGE]
+
+ See the p4a source file 'permissions.py' for a list of valid permission
+ strings (pythonforandroid/recipes/android/src/android/permissions.py).
+
+ Any callback supplied must accept two arguments:
+ permissions (list of str): A list of permission strings
+ grant_results (list of bool): A list of bools indicating whether the
+ respective permission was granted.
+ See Android documentation for onPermissionsCallbackResult for
+ further information.
+
+ Note that if the request is interupted the callback may contain an empty
+ list of permissions, without permissions being granted; the App should
+ check that each permission requested has been granted.
+
+ Also note that when calling request_permission on SDK_INT < 23, the
+ callback will be returned immediately as requesting permissions is not
+ required.
+ """
+ _RequestPermissionsManager.request_permissions(permissions, callback)
+
+
+def request_permission(permission, callback=None):
+ request_permissions([permission], callback)
+
+
+def check_permission(permission):
+ """Checks if an app holds the passed permission.
+
+ Args:
+ - permission An Android permission (str)
+
+ Returns:
+ bool: True if the app holds the permission given, False otherwise.
+ """
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ result = bool(mActivity.checkCurrentPermission(
+ permission + ""
+ ))
+ return result
diff --git a/recipes/android/src/android/runnable.py b/recipes/android/src/android/runnable.py
index 564d83b..b20f6cc 100644
--- a/recipes/android/src/android/runnable.py
+++ b/recipes/android/src/android/runnable.py
@@ -1,14 +1,17 @@
'''
Runnable
========
-
'''
from jnius import PythonJavaClass, java_method, autoclass
-from android.config import JAVA_NAMESPACE
+from android.config import ACTIVITY_CLASS_NAME
-# reference to the activity
-_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+# Reference to the activity
+_PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
+
+# Cache of functions table. In older Android versions the number of JNI references
+# is limited, so by caching them we avoid running out.
+__functionstable__ = {}
class Runnable(PythonJavaClass):
@@ -20,7 +23,7 @@ class Runnable(PythonJavaClass):
__runnables__ = []
def __init__(self, func):
- super(Runnable, self).__init__()
+ super().__init__()
self.func = func
def __call__(self, *args, **kwargs):
@@ -33,16 +36,23 @@ 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.
'''
+ if f not in __functionstable__:
+ rfunction = Runnable(f) # store the runnable function
+ __functionstable__[f] = {"rfunction": rfunction}
+ rfunction = __functionstable__[f]["rfunction"]
+
def f2(*args, **kwargs):
- Runnable(f)(*args, **kwargs)
+ rfunction(*args, **kwargs)
+
return f2
diff --git a/recipes/android/src/android/storage.py b/recipes/android/src/android/storage.py
new file mode 100644
index 0000000..aa6d781
--- /dev/null
+++ b/recipes/android/src/android/storage.py
@@ -0,0 +1,117 @@
+from jnius import autoclass, cast
+import os
+
+from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
+
+
+Environment = autoclass('android.os.Environment')
+File = autoclass('java.io.File')
+
+
+def _android_has_is_removable_func():
+ VERSION = autoclass('android.os.Build$VERSION')
+ return (VERSION.SDK_INT >= 24)
+
+
+def _get_sdcard_path():
+ """ Internal function to return getExternalStorageDirectory()
+ path. This is internal because it may either return the internal,
+ or an external sd card, depending on the device.
+ Use primary_external_storage_path()
+ or secondary_external_storage_path() instead which try to
+ distinguish this properly.
+ """
+ return (
+ Environment.getExternalStorageDirectory().getAbsolutePath()
+ )
+
+
+def _get_activity():
+ """
+ Retrieves the activity from `PythonActivity` fallback to `PythonService`.
+ """
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
+ activity = PythonActivity.mActivity
+ if activity is None:
+ # assume we're running from the background service
+ PythonService = autoclass(SERVICE_CLASS_NAME)
+ activity = PythonService.mService
+ return activity
+
+
+def app_storage_path():
+ """ Locate the built-in device storage used for this app only.
+
+ This storage is APP-SPECIFIC, and not visible to other apps.
+ It will be wiped when your app is uninstalled.
+
+ Returns directory path to storage.
+ """
+ activity = _get_activity()
+ currentActivity = cast('android.app.Activity', activity)
+ context = cast('android.content.ContextWrapper',
+ currentActivity.getApplicationContext())
+ file_p = cast('java.io.File', context.getFilesDir())
+ return os.path.normpath(os.path.abspath(
+ file_p.getAbsolutePath().replace("/", os.path.sep)))
+
+
+def primary_external_storage_path():
+ """ Locate the built-in device storage that user can see via file browser.
+ Often found at: /sdcard/
+
+ This is storage is SHARED, and visible to other apps and the user.
+ It will remain untouched when your app is uninstalled.
+
+ Returns directory path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ """
+ if _android_has_is_removable_func():
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if not Environment.isExternalStorageRemovable(File(sdpath)):
+ return sdpath
+ if "EXTERNAL_STORAGE" in os.environ:
+ return os.environ["EXTERNAL_STORAGE"]
+ raise RuntimeError(
+ "unexpectedly failed to determine " +
+ "primary external storage path"
+ )
+
+
+def secondary_external_storage_path():
+ """ Locate the external SD Card storage, which may not be present.
+ Often found at: /sdcard/External_SD/
+
+ This storage is SHARED, visible to other apps, and may not be
+ be available if the user didn't put in an external SD card.
+ It will remain untouched when your app is uninstalled.
+
+ Returns None if not found, otherwise path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ If it is not writable and presents as empty even with
+ permissions, then the external sd card may not be present.
+ """
+ if _android_has_is_removable_func:
+ # See if getExternalStorageDirectory() returns secondary ext storage:
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if Environment.isExternalStorageRemovable(File(sdpath)):
+ if os.path.exists(sdpath):
+ return sdpath
+
+ # See if we can take a guess based on environment variables:
+ p = None
+ if "SECONDARY_STORAGE" in os.environ:
+ p = os.environ["SECONDARY_STORAGE"]
+ elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
+ p = os.environ["EXTERNAL_SDCARD_STORAGE"]
+ if p is not None and os.path.exists(p):
+ return p
+ return None
diff --git a/recipes/android/src/setup.py b/recipes/android/src/setup.py
index 47ef6a9..bcd411f 100755
--- a/recipes/android/src/setup.py
+++ b/recipes/android/src/setup.py
@@ -3,15 +3,9 @@ import os
library_dirs = ['libs/' + os.environ['ARCH']]
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 []
-
-renpy_sound = Extension('android._android_sound',
- ['android/_android_sound.c', 'android/_android_sound_jni.c', ],
- libraries=sdl_libs + ['log'],
- library_dirs=library_dirs)
+sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])
modules = [Extension('android._android',
['android/_android.c', 'android/_android_jni.c'],
@@ -22,10 +16,6 @@ modules = [Extension('android._android',
libraries=['log'],
library_dirs=library_dirs)]
-if int(os.environ['IS_PYGAME']):
- modules.append(renpy_sound)
-
-
setup(name='android',
version='1.0',
packages=['android'],
diff --git a/recipes/cffi/__init__.py b/recipes/cffi/__init__.py
deleted file mode 100644
index be23a03..0000000
--- a/recipes/cffi/__init__.py
+++ /dev/null
@@ -1,58 +0,0 @@
-
-import os
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-
-
-class CffiRecipe(CompiledComponentsPythonRecipe):
- name = 'cffi'
- version = '1.14.6'
- url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
-
- depends = [('python2', 'python3crystax'), 'setuptools', 'pycparser', 'libffi']
-
- patches = ['disable-pkg-config.patch']
-
- # call_hostpython_via_targetpython = False
- install_in_hostpython = True
-
- 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)
- # sets linker to use the correct gcc (cross compiler)
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- 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['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
-
- # required for libc and libdl/include
- 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'],
- ])
- if self.ctx.ndk == 'crystax':
- # only keeps major.minor (discards patch)
- python_version = self.ctx.python_recipe.version[0:3]
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
- return env
-
-
-recipe = CffiRecipe()
\ No newline at end of file
diff --git a/recipes/cffi/disable-pkg-config.patch b/recipes/cffi/disable-pkg-config.patch
deleted file mode 100644
index cf2abd5..0000000
--- a/recipes/cffi/disable-pkg-config.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-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 = os.environ['FFI_INC'].split(",") if 'FFI_INC' in os.environ else []
- define_macros = []
- library_dirs = []
- extra_compile_args = []
-@@ -67,14 +66,7 @@ def ask_supports_thread():
- sys.stderr.write("The above error message can be safely ignored\n")
-
- def use_pkg_config():
-- if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):
-- use_homebrew_for_libffi()
--
-- _ask_pkg_config(include_dirs, '--cflags-only-I', '-I', sysroot=True)
-- _ask_pkg_config(extra_compile_args, '--cflags-only-other')
-- _ask_pkg_config(library_dirs, '--libs-only-L', '-L', sysroot=True)
-- _ask_pkg_config(extra_link_args, '--libs-only-other')
-- _ask_pkg_config(libraries, '--libs-only-l', '-l')
-+ pass
-
- def use_homebrew_for_libffi():
- # We can build by setting:
diff --git a/recipes/coincurve/__init__.py b/recipes/coincurve/__init__.py
index 5be9557..8e98ca7 100644
--- a/recipes/coincurve/__init__.py
+++ b/recipes/coincurve/__init__.py
@@ -3,17 +3,50 @@ from pythonforandroid.recipe import PythonRecipe, CompiledComponentsPythonRecipe
class CoincurveRecipe(CompiledComponentsPythonRecipe):
- version = '7.1.0'
- url = 'https://github.com/ofek/coincurve/archive/{version}.tar.gz'
+ # version = '15.0.0'
+ # url = 'https://github.com/ofek/coincurve/archive/{version}.tar.gz'
+ # call_hostpython_via_targetpython = False
+ # depends = ['setuptools',
+ # 'libffi', 'cffi', 'libsecp256k1']
+ # patches = [
+ # "cross_compile.patch", "drop_setup_requires.patch",
+ # "find_lib.patch", "no-download.patch", "setup.py.patch"]
+ #
+ # def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ # env = super().get_recipe_env(arch, with_flags_in_cc)
+ # # sets linker to use the correct gcc (cross compiler)
+ # env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
+ # libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)
+ # libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)
+ # env['LDFLAGS'] += ' -L{}'.format(os.path.join(libsecp256k1_dir, '.libs'))
+ # env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include')
+ # # only keeps major.minor (discards patch)
+ # python_version = self.ctx.python_recipe.version[0:3]
+ # # required additional library and path for Crystax
+ # if self.ctx.ndk == 'crystax':
+ # ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
+ # env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
+ # env['LDFLAGS'] += ' -lpython{}'.format(python_version)
+ # # until `pythonforandroid/archs.py` gets merged upstream:
+ # # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
+ # env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
+ # else:
+ # env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
+ # env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python{}'.format(python_version)
+ # env['LDFLAGS'] += " -lpython{}".format(python_version)
+ # env['LDFLAGS'] += " -lsecp256k1"
+ # return env
+ version = 'v15.0.1'
+ url = 'https://github.com/ofek/coincurve/archive/refs/tags/v15.0.1.tar.gz'
call_hostpython_via_targetpython = False
- depends = [('python2', 'python3crystax'), 'setuptools',
- 'libffi', 'cffi', 'libsecp256k1']
+ depends = [('python2', 'python3'), 'setuptools',
+ 'libffi', 'cffi', 'libsecp256k1']
patches = [
"cross_compile.patch", "drop_setup_requires.patch",
- "find_lib.patch", "no-download.patch", "setup.py.patch"]
+ "find_lib.patch", "no-download.patch"]
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(CoincurveRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
# sets linker to use the correct gcc (cross compiler)
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)
@@ -23,17 +56,17 @@ class CoincurveRecipe(CompiledComponentsPythonRecipe):
# only keeps major.minor (discards patch)
python_version = self.ctx.python_recipe.version[0:3]
# required additional library and path for Crystax
- if self.ctx.ndk == 'crystax':
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
- else:
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python{}'.format(python_version)
- env['LDFLAGS'] += " -lpython{}".format(python_version)
+ # if self.ctx.ndk == 'crystax':
+ # ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
+ # env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
+ # env['LDFLAGS'] += ' -lpython{}m'.format(python_version)
+ # # until `pythonforandroid/archs.py` gets merged upstream:
+ # # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
+ # env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
+ # else:
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
+ env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python{}'.format(python_version)
+ env['LDFLAGS'] += " -lpython{}".format(python_version)
env['LDFLAGS'] += " -lsecp256k1"
return env
diff --git a/recipes/coincurve/cross_compile.patch b/recipes/coincurve/cross_compile.patch
index fbbdd49..6c37191 100644
--- a/recipes/coincurve/cross_compile.patch
+++ b/recipes/coincurve/cross_compile.patch
@@ -1,12 +1,12 @@
diff --git a/setup.py b/setup.py
-index c224fb2..bf925bd 100644
+index f0d3ab3..ebb1b4f 100644
--- a/setup.py
+++ b/setup.py
-@@ -182,6 +182,7 @@ class build_clib(_build_clib):
+@@ -177,6 +177,7 @@ class build_clib(_build_clib):
'--disable-dependency-tracking',
'--with-pic',
'--enable-module-recovery',
+ "--host=%s" % os.environ['TOOLCHAIN_PREFIX'],
- '--disable-jni',
'--prefix',
os.path.abspath(self.build_clib),
+ '--enable-experimental',
diff --git a/recipes/coincurve/drop_setup_requires.patch b/recipes/coincurve/drop_setup_requires.patch
index 9994b3f..73b66c1 100644
--- a/recipes/coincurve/drop_setup_requires.patch
+++ b/recipes/coincurve/drop_setup_requires.patch
@@ -1,12 +1,12 @@
diff --git a/setup.py b/setup.py
-index c224fb2..466e789 100644
+index f0d3ab3..05c9ee0 100644
--- a/setup.py
+++ b/setup.py
-@@ -250,7 +250,6 @@ else:
- def has_c_libraries(self):
+@@ -243,7 +243,6 @@ else:
return not has_system_lib()
+
setup_kwargs = dict(
-- setup_requires=['cffi>=1.3.0', 'pytest-runner>=2.6.2'],
+- setup_requires=['cffi>=1.3.0', 'requests'],
ext_package='coincurve',
cffi_modules=['_cffi_build/build.py:ffi'],
cmdclass={
diff --git a/recipes/coincurve/find_lib.patch b/recipes/coincurve/find_lib.patch
index 3d3c41d..63ef30c 100644
--- a/recipes/coincurve/find_lib.patch
+++ b/recipes/coincurve/find_lib.patch
@@ -1,13 +1,13 @@
diff --git a/setup_support.py b/setup_support.py
-index e7a4f2e..72f0c4d 100644
+index fcd1d9d..0ac3bc0 100644
--- a/setup_support.py
+++ b/setup_support.py
-@@ -68,6 +69,8 @@ def build_flags(library, type_, path):
-
-
+@@ -60,6 +60,8 @@ def build_flags(library, type_, path):
+
+
def _find_lib():
+ # we're picking up the recipe one
+ return True
- from cffi import FFI
- ffi = FFI()
- try:
+ if 'COINCURVE_IGNORE_SYSTEM_LIB' in os.environ:
+ return False
+
diff --git a/recipes/coincurve/no-download.patch b/recipes/coincurve/no-download.patch
index fcf4d20..99540a6 100644
--- a/recipes/coincurve/no-download.patch
+++ b/recipes/coincurve/no-download.patch
@@ -1,8 +1,8 @@
diff --git a/setup.py b/setup.py
-index c224fb2..d5f6d1a 100644
+index f0d3ab3..1d81b43 100644
--- a/setup.py
+++ b/setup.py
-@@ -51,6 +51,8 @@ if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]:
+@@ -47,6 +47,8 @@ if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]:
def download_library(command):
diff --git a/recipes/coincurve/setup.py.patch b/recipes/coincurve/setup.py.patch
deleted file mode 100644
index bdc7ab4..0000000
--- a/recipes/coincurve/setup.py.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From bf3a0684e9b0af29d9777f61d6e7e1d3cc0f2803 Mon Sep 17 00:00:00 2001
-From: Kieran Prasch
-Date: Thu, 19 Jul 2018 14:11:48 -0700
-Subject: [PATCH] Exclude tests in setup.py
-
----
- setup.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/setup.py b/setup.py
-index 0b579f1..0a793ed 100644
---- a/setup.py
-+++ b/setup.py
-@@ -277,7 +277,7 @@ def has_c_libraries(self):
- install_requires=['asn1crypto', 'cffi>=1.3.0'],
- tests_require=['pytest>=2.8.7'],
-
-- packages=find_packages(exclude=('_cffi_build', '_cffi_build.*', 'libsecp256k1')),
-+ packages=find_packages(exclude=('_cffi_build', '_cffi_build.*', 'libsecp256k1', 'tests')),
-
- distclass=Distribution,
- zip_safe=False,
diff --git a/recipes/cryptography/__init__.py b/recipes/cryptography/__init__.py
deleted file mode 100644
index 148a121..0000000
--- a/recipes/cryptography/__init__.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
-from os.path import dirname, join
-import os
-
-class CryptographyRecipe(CompiledComponentsPythonRecipe):
- name = 'cryptography'
- version = '3.1.1'
- url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'openssl', 'six', 'setuptools', 'cffi']
- call_hostpython_via_targetpython = False
- patches = ['libpthread.patch']
-
- 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['CFLAGS'] += ' -I' + join(openssl_dir, 'include') + ' -w'
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + openssl_dir + \
- ' -lssl' + r.version + \
- ' -lcrypto' + r.version
-
- if self.ctx.ndk == 'crystax':
- # only keeps major.minor (discards patch)
- python_version = self.ctx.python_recipe.version[0:3]
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) + ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
-
-
- return env
-
-recipe = CryptographyRecipe()
diff --git a/recipes/cryptography/libpthread.patch b/recipes/cryptography/libpthread.patch
deleted file mode 100644
index dcdedd8..0000000
--- a/recipes/cryptography/libpthread.patch
+++ /dev/null
@@ -1,16 +0,0 @@
-diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
-index 35ccd6b..6d5ee4c 100755
---- a/src/_cffi_src/build_openssl.py
-+++ b/src/_cffi_src/build_openssl.py
-@@ -42,10 +42,7 @@ def _get_openssl_libraries(platform):
- # -lpthread required due to usage of pthread an potential
- # existance of a static part containing e.g. pthread_atfork
- # (https://github.com/pyca/cryptography/issues/5084)
-- if sys.platform == "zos":
-- return ["ssl", "crypto"]
-- else:
-- return ["ssl", "crypto", "pthread"]
-+ return ["ssl", "crypto"]
-
-
- def _extra_compile_args(platform):
diff --git a/recipes/hostpython3crystax/__init__.py b/recipes/hostpython3crystax/__init__.py
deleted file mode 100644
index 2e4e0a7..0000000
--- a/recipes/hostpython3crystax/__init__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from pythonforandroid.recipe import Recipe
-from pythonforandroid.toolchain import shprint
-from os.path import join
-import sh
-
-
-class Hostpython3Recipe(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()
diff --git a/recipes/libffi/Application.mk b/recipes/libffi/Application.mk
deleted file mode 100644
index 599da11..0000000
--- a/recipes/libffi/Application.mk
+++ /dev/null
@@ -1,3 +0,0 @@
-APP_OPTIM := release
-APP_ABI := all # or armeabi
-APP_MODULES := libffi
\ No newline at end of file
diff --git a/recipes/libffi/__init__.py b/recipes/libffi/__init__.py
deleted file mode 100644
index 31ed9c6..0000000
--- a/recipes/libffi/__init__.py
+++ /dev/null
@@ -1,53 +0,0 @@
-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, ensure_dir
-import sh
-
-
-class LibffiRecipe(Recipe):
- """
- 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',
- # 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 should_build(self, arch):
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so'))
-
- 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)
-
- shprint(sh.make, '-j', str(cpu_count()), '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))
-
- def get_include_dirs(self, arch):
- return [join(self.get_build_dir(arch.arch), 'include')]
-
-
-recipe = LibffiRecipe()
diff --git a/recipes/libffi/disable-mips-check.patch b/recipes/libffi/disable-mips-check.patch
deleted file mode 100644
index 0f727ba..0000000
--- a/recipes/libffi/disable-mips-check.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-diff -Naur libffi/Android.mk b/Android.mk
---- libffi/Android.mk 2015-12-22 17:00:48.025478556 -0600
-+++ b/Android.mk 2015-12-22 17:02:23.999249390 -0600
-@@ -23,23 +23,20 @@
- # Build rules for the target.
- #
-
--# We only build ffi for mips.
--ifeq ($(TARGET_ARCH),mips)
-
-- include $(CLEAR_VARS)
-+include $(CLEAR_VARS)
-
-- ffi_arch := $(TARGET_ARCH)
-- ffi_os := $(TARGET_OS)
-+ffi_arch := $(TARGET_ARCH)
-+ffi_os := $(TARGET_OS)
-
-- # This include just keeps the nesting a bit saner.
-- include $(LOCAL_PATH)/Libffi.mk
-+# This include just keeps the nesting a bit saner.
-+include $(LOCAL_PATH)/Libffi.mk
-
-- LOCAL_MODULE_TAGS := optional
-- LOCAL_MODULE := libffi
-+LOCAL_MODULE_TAGS := optional
-+LOCAL_MODULE := libffi
-
-- include $(BUILD_SHARED_LIBRARY)
-+include $(BUILD_SHARED_LIBRARY)
-
--endif
-
- # Also include the rules for the test suite.
- include external/libffi/testsuite/Android.mk
diff --git a/recipes/libffi/fix-includedir.patch b/recipes/libffi/fix-includedir.patch
deleted file mode 100644
index 0dc35c7..0000000
--- a/recipes/libffi/fix-includedir.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-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/recipes/libffi/remove-version-info.patch b/recipes/libffi/remove-version-info.patch
deleted file mode 100644
index 7bdc11a..0000000
--- a/recipes/libffi/remove-version-info.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff -Naur libffi/Makefile.am b/Makefile.am
---- libffi/Makefile.am 2014-11-12 06:00:59.000000000 -0600
-+++ b/Makefile.am 2015-12-23 15:57:10.363148806 -0600
-@@ -249,7 +249,7 @@
- AM_CFLAGS += -DFFI_DEBUG
- endif
-
--libffi_la_LDFLAGS = -no-undefined -version-info `grep -v '^\#' $(srcdir)/libtool-version` $(LTLDFLAGS) $(AM_LTLDFLAGS)
-+libffi_la_LDFLAGS = -no-undefined -avoid-version $(LTLDFLAGS) $(AM_LTLDFLAGS)
-
- AM_CPPFLAGS = -I. -I$(top_srcdir)/include -Iinclude -I$(top_srcdir)/src
- AM_CCASFLAGS = $(AM_CPPFLAGS)
diff --git a/recipes/libsecp256k1/__init__.py b/recipes/libsecp256k1/__init__.py
deleted file mode 100644
index 69349d9..0000000
--- a/recipes/libsecp256k1/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from pythonforandroid.toolchain import shprint, current_directory
-from pythonforandroid.recipe import Recipe
-from multiprocessing import cpu_count
-from os.path import exists
-import sh
-
-
-class LibSecp256k1Recipe(Recipe):
-
- version = 'b0452e6'
- url = 'https://github.com/bitcoin-core/secp256k1/archive/{version}.zip'
-
- def build_arch(self, arch):
- super(LibSecp256k1Recipe, 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('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
- '--enable-shared',
- '--enable-module-recovery',
- '--enable-experimental',
- '--enable-module-ecdh',
- _env=env)
- shprint(sh.make, '-j' + str(cpu_count()), _env=env)
- libs = ['.libs/libsecp256k1.so']
- self.install_libs(arch, *libs)
-
-
-recipe = LibSecp256k1Recipe()
diff --git a/recipes/netifaces/__init__.py b/recipes/netifaces/__init__.py
deleted file mode 100644
index 26ba1d6..0000000
--- a/recipes/netifaces/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import glob
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
-from os.path import join
-import os
-import sh
-
-
-class NetifacesRecipe(CompiledComponentsPythonRecipe):
- version = '0.10.7'
- url = 'https://files.pythonhosted.org/packages/81/39/4e9a026265ba944ddf1fea176dbb29e0fe50c43717ba4fcf3646d099fe38/netifaces-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
- call_hostpython_via_targetpython = False
- patches = ['socket-ioctls.patch']
-
- def get_recipe_env(self, arch):
- env = super(NetifacesRecipe, self).get_recipe_env(arch)
-
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
-
- if self.ctx.ndk == 'crystax':
- # only keeps major.minor (discards patch)
- python_version = self.ctx.python_recipe.version
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
-
- return env
-
-recipe = NetifacesRecipe()
diff --git a/recipes/netifaces/socket-ioctls.patch b/recipes/netifaces/socket-ioctls.patch
deleted file mode 100644
index 4e6da70..0000000
--- a/recipes/netifaces/socket-ioctls.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/netifaces.c 2018-09-25 21:26:48.238476102 +0100
-+++ b/netifaces.c 2018-09-25 21:55:14.201995669 +0100
-@@ -22,6 +22,10 @@
- obj = Py_InitModule3((name), (methods), (doc));
- #endif
-
-+#ifndef HAVE_SOCKET_IOCTLS
-+#define HAVE_SOCKET_IOCTLS 1
-+#endif
-+
- #ifndef WIN32
-
- # include
-@@ -178,12 +182,12 @@
- # include
- # endif /* HAVE_GETIFADDRS */
-
--# if !HAVE_GETIFADDRS && (!HAVE_SOCKET_IOCTLS || !HAVE_SIOCGIFCONF)
-+//# if !HAVE_GETIFADDRS && (!HAVE_SOCKET_IOCTLS || !HAVE_SIOCGIFCONF)
- /* If the platform doesn't define, what we need, barf. If you're seeing this,
- it means you need to write suitable code to retrieve interface information
- on your system. */
--# error You need to add code for your platform.
--# endif
-+//# error You need to add code for your platform.
-+//# endif
-
- #else /* defined(WIN32) */
-
diff --git a/recipes/openssl/__init__.py b/recipes/openssl/__init__.py
deleted file mode 100644
index 5d95311..0000000
--- a/recipes/openssl/__init__.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from functools import partial
-
-from pythonforandroid.recipe import Recipe
-from pythonforandroid.toolchain import shprint, current_directory
-from os.path import join
-import sh
-
-
-class OpenSSLRecipe(Recipe):
- version = '1.1'
- url_version = '1.1.1k'
- url = 'https://www.openssl.org/source/openssl-{url_version}.tar.gz'
-
- @property
- def versioned_url(self):
- if self.url is None:
- return None
- return self.url.format(url_version=self.url_version)
-
- 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['OPENSSL_VERSION'] = self.version
- env['MAKE'] = 'make' # This removes the '-j5', which isn't safe
- env['CFLAGS'] += ' ' + env['LDFLAGS']
- env['CC'] += ' ' + env['LDFLAGS']
- env['ANDROID_NDK'] = self.ctx.ndk_dir
- return env
-
- def select_build_arch(self, arch):
- aname = arch.arch
- if 'arm64' in aname:
- return 'android-arm64'
- if 'v7a' in aname:
- return 'android-arm'
- if 'arm' in aname:
- return 'android'
- return 'linux-armv4'
-
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
- with current_directory(self.get_build_dir(arch.arch)):
- # sh fails with code 255 trying to execute ./Configure
- # so instead we manually run perl passing in Configure
- perl = sh.Command('perl')
- buildarch = self.select_build_arch(arch)
-
- config_args = ['shared', 'no-dso', 'no-asm']
- config_args.append(buildarch)
- shprint(perl, 'Configure', *config_args, _env=env)
- self.apply_patch('disable-sover.patch', arch.arch)
-
- makefile = join(self.get_build_dir(arch.arch), 'Makefile')
- sh.sed('-i', 's/CROSS_COMPILE=arm-linux-androideabi-/CROSS_COMPILE=/g', makefile)
- sh.sed('-i', 's/CROSS_COMPILE=aarch64-linux-android-/CROSS_COMPILE=/g', makefile)
- shprint(sh.make, 'build_libs', _env=env)
-
- self.install_libs(arch, 'libssl.a', 'libssl' + self.version + '.so',
- 'libcrypto.a', 'libcrypto' + self.version + '.so')
-
-recipe = OpenSSLRecipe()
diff --git a/recipes/openssl/disable-sover.patch b/recipes/openssl/disable-sover.patch
deleted file mode 100644
index bbfc37d..0000000
--- a/recipes/openssl/disable-sover.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- 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=
-
\ No newline at end of file
diff --git a/recipes/openssl/rename-shared-lib.patch b/recipes/openssl/rename-shared-lib.patch
deleted file mode 100644
index 30c0f79..0000000
--- a/recipes/openssl/rename-shared-lib.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- openssl/Makefile.shared 2016-05-03 15:44:42.000000000 +0200
-+++ patch/Makefile.shared 2016-07-14 00:08:37.268792948 +0200
-@@ -147,11 +147,11 @@
- DETECT_GNU_LD=($(CC) -Wl,-V /dev/null 2>&1 | grep '^GNU ld' )>/dev/null
-
- DO_GNU_SO=$(CALC_VERSIONS); \
-- SHLIB=lib$(LIBNAME).so; \
-+ SHLIB=lib$(LIBNAME)$(OPENSSL_VERSION).so; \
- SHLIB_SUFFIX=; \
- ALLSYMSFLAGS='-Wl,--whole-archive'; \
- NOALLSYMSFLAGS='-Wl,--no-whole-archive'; \
-- SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB$$SHLIB_SOVER$$SHLIB_SUFFIX"
-+ SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB"
-
- DO_GNU_APP=LDFLAGS="$(CFLAGS) -Wl,-rpath,$(LIBRPATH)"
-
diff --git a/recipes/pyjnius/__init__.py b/recipes/pyjnius/__init__.py
deleted file mode 100644
index 3100519..0000000
--- a/recipes/pyjnius/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-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 = '1.3.0'
- url = 'https://github.com/kivy/pyjnius/archive/{version}.zip'
- name = 'pyjnius'
- depends = [('genericndkbuild', 'sdl2', 'sdl'), 'six']
- site_packages_name = 'jnius'
-
- patches = [('sdl2_jnienv_getter.patch', will_build('sdl2')),
- ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))]
-
- def get_recipe_env(self, arch):
- env = super(PyjniusRecipe, self).get_recipe_env(arch)
-
- return env
-
- def postbuild_arch(self, arch):
- super(PyjniusRecipe, self).postbuild_arch(arch)
- info('Copying pyjnius java class to classes build dir')
- with current_directory(self.get_build_dir(arch.arch)):
- shprint(sh.cp, '-a', join('jnius', 'src', 'org'), self.ctx.javaclass_dir)
-
-
-recipe = PyjniusRecipe()
diff --git a/recipes/pyjnius/genericndkbuild_jnienv_getter.patch b/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
deleted file mode 100644
index 8ea7ab2..0000000
--- a/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
- # on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
-+cdef extern JNIEnv *WebView_AndroidGetJNIEnv()
-
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return WebView_AndroidGetJNIEnv()
-diff --git a/jnius/env.py b/jnius/env.py
---- a/jnius/env.py
-+++ b/jnius/env.py
-@@ -185,10 +185,10 @@ except ImportError:
-
- def get_libraries(platform):
- if platform == 'android':
- # for android, we use SDL...
-- return ['sdl', 'log']
-+ return ['main', 'log']
-
- elif platform == 'win32':
- return ['jvm']
diff --git a/recipes/pyjnius/sdl2_jnienv_getter.patch b/recipes/pyjnius/sdl2_jnienv_getter.patch
deleted file mode 100644
index 7ed847e..0000000
--- a/recipes/pyjnius/sdl2_jnienv_getter.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
- # on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
-+cdef extern JNIEnv *SDL_AndroidGetJNIEnv()
-
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return SDL_AndroidGetJNIEnv()
-diff --git a/env.py b/env.py
-index 740510f..0c8e55f 100644
---- a/jnius/env.py
-+++ b/jnius/env.py
-@@ -185,10 +185,10 @@ except ImportError:
-
- def get_libraries(platform):
- if platform == 'android':
- # for android, we use SDL...
-- return ['sdl', 'log']
-+ return ['SDL2', 'log']
-
- elif platform == 'win32':
- return ['jvm']
diff --git a/recipes/python3crystax/__init__.py b/recipes/python3crystax/__init__.py
deleted file mode 100644
index 85ed3bd..0000000
--- a/recipes/python3crystax/__init__.py
+++ /dev/null
@@ -1,377 +0,0 @@
-from pythonforandroid.recipe import TargetPythonRecipe
-from pythonforandroid.toolchain import shprint, current_directory
-from pythonforandroid.logger import info, error
-from pythonforandroid.util import ensure_dir, temp_directory
-from os.path import exists, join
-import os
-import glob
-import sh
-from sh import Command
-
-# This is the content of opensslconf.h taken from
-# ndkdir/build/tools/build-target-openssl.sh
-OPENSSLCONF = """#if defined(__ARM_ARCH_5TE__)
-#include "opensslconf_armeabi.h"
-#elif defined(__ARM_ARCH_7A__) && !defined(__ARM_PCS_VFP)
-#include "opensslconf_armeabi_v7a.h"
-#elif defined(__ARM_ARCH_7A__) && defined(__ARM_PCS_VFP)
-#include "opensslconf_armeabi_v7a_hard.h"
-#elif defined(__aarch64__)
-#include "opensslconf_arm64_v8a.h"
-#elif defined(__i386__)
-#include "opensslconf_x86.h"
-#elif defined(__x86_64__)
-#include "opensslconf_x86_64.h"
-#elif defined(__mips__) && !defined(__mips64)
-#include "opensslconf_mips.h"
-#elif defined(__mips__) && defined(__mips64)
-#include "opensslconf_mips64.h"
-#else
-#error "Unsupported ABI"
-#endif
-"""
-LATEST_FULL_VERSION = {
- '3.5': '3.5.1',
- '3.6': '3.6.6',
- '3.7': '3.7.1',
- '3.9': '3.9.6'
-}
-
-def realpath(fname):
- """
- Own implementation of os.realpath which may be broken in some python versions
- Returns: the absolute path o
-
- """
-
- if not os.path.islink(fname):
- return os.path.abspath(fname)
-
- abs_path = os.path.abspath(fname).split(os.sep)[:-1]
- rel_path = os.readlink(fname)
-
- if os.path.abspath(rel_path) == rel_path:
- return rel_path
-
- rel_path = rel_path.split(os.sep)
- for folder in rel_path:
- if folder == '..':
- abs_path.pop()
- else:
- abs_path.append(folder)
- return os.sep.join(abs_path)
-
-class Python3Recipe(TargetPythonRecipe):
- version = '3.9'
- url = ''
- name = 'python3crystax'
-
- depends = ['hostpython3crystax', 'sqlite3', 'openssl']
- conflicts = ['python2', 'python3']
-
- from_crystax = True
-
- def download_if_necessary(self):
- if 'openssl' in self.ctx.recipe_build_order or self.version in ('3.6', '3.7', '3.9'):
- full_version = LATEST_FULL_VERSION[self.version]
- Python3Recipe.url = 'https://www.python.org/ftp/python/{0}.{1}.{2}/Python-{0}.{1}.{2}.tgz'.format(*full_version.split('.'))
- super(Python3Recipe, self).download_if_necessary()
-
- def get_dir_name(self):
- name = super(Python3Recipe, self).get_dir_name()
- name += '-version{}'.format(self.version)
- return name
-
- def copy_include_dir(self, source, target):
- ensure_dir(target)
- for fname in os.listdir(source):
- sh.ln('-sf', realpath(join(source, fname)), join(target, fname))
-
- def _patch_dev_defaults(self, fp, target_ver):
- for line in fp:
- if 'OPENSSL_VERSIONS=' in line:
- versions = line.split('"')[1].split(' ')
- if versions[0] == target_ver:
- raise ValueError('Patch not needed')
-
- if target_ver in versions:
- versions.remove(target_ver)
-
- versions.insert(0, target_ver)
-
- yield 'OPENSSL_VERSIONS="{}"\n'.format(' '.join(versions))
- else:
- yield line
-
- def patch_dev_defaults(self, ssl_recipe):
- def_fname = join(self.ctx.ndk_dir, 'build', 'tools', 'dev-defaults.sh')
- try:
- with open(def_fname, 'r') as fp:
- s = ''.join(self._patch_dev_defaults(fp,
- str(ssl_recipe.version)))
- with open(def_fname, 'w') as fp:
- fp.write(s)
-
- except ValueError:
- pass
-
- def include_root(self, arch_name):
- return join(self.ctx.ndk_dir, 'sources', 'python', self.major_minor_version_string,
- 'include', 'python')
-
- def link_root(self, arch_name):
- return join(self.ctx.ndk_dir, 'sources', 'python', self.major_minor_version_string,
- 'libs', arch_name)
-
- def check_for_sqlite3so(self, sqlite_recipe, arch):
- dynlib_dir = join(self.ctx.ndk_dir, 'sources', 'python', self.version,
- 'libs', arch.arch, 'modules')
-
- if os.path.exists(join(dynlib_dir, 'libsqlite3.so')):
- return 10, 'Shared object exists in ndk'
-
- major_version = sqlite_recipe.version.split('.')[0]
- # find out why _ssl.so is missin
- source_dir = join(self.ctx.ndk_dir, 'sources', 'sqlite', major_version)
- if not os.path.exists(source_dir):
- return 0, 'sqlite3 version not present'
-
- # these two path checks are lifted straight from:
- # crystax-ndk/build/tools/build-target-python.sh
- if not os.path.exists(join(source_dir, 'Android.mk')):
- return 1.1, 'Android.mk is missing in sqlite3 source'
-
- include_dir = join(source_dir, 'include')
- if not os.path.exists(join(include_dir, 'sqlite3.h')):
- return 1.2, 'sqlite3 include dir missing'
-
- # lastly a check to see whether shared objects for the correct arch
- # is present in the ndk
- if not os.path.exists(join(source_dir, 'libs', arch.arch, 'libsqlite3.a')):
- return 2, 'sqlite3 libs for this arch is missing in ndk'
-
- return 5, 'Ready to recompile python'
-
-
- def check_for_sslso(self, ssl_recipe, arch):
- # type: (Recipe, str)
- dynlib_dir = join(self.ctx.ndk_dir, 'sources', 'python', self.version,
- 'libs', arch.arch, 'modules')
-
- if os.path.exists(join(dynlib_dir, '_ssl.so')):
- return 10, 'Shared object exists in ndk'
-
- # find out why _ssl.so is missing
- source_dir = join(self.ctx.ndk_dir, 'sources', 'openssl', ssl_recipe.version)
- if not os.path.exists(source_dir):
- return 0, 'Openssl version not present'
-
- # these two path checks are lifted straight from:
- # crystax-ndk/build/tools/build-target-python.sh
- if not os.path.exists(join(source_dir, 'Android.mk')):
- return 1.1, 'Android.mk is missing in openssl source'
-
- include_dir = join(source_dir, 'include','openssl')
- if not os.path.exists(join(include_dir, 'opensslconf.h')):
- return 1.2, 'Openssl include dir missing'
-
- under_scored_arch = arch.arch.replace('-', '_')
- if not os.path.lexists(join(include_dir,
- 'opensslconf_{}.h'.format(under_scored_arch))):
- return 1.3, 'Opensslconf arch header missing from include'
-
- # lastly a check to see whether shared objects for the correct arch
- # is present in the ndk
- if not os.path.exists(join(source_dir, 'libs', arch.arch)):
- return 2, 'Openssl libs for this arch is missing in ndk'
-
- return 5, 'Ready to recompile python'
-
- def find_Android_mk(self):
- openssl_dir = join(self.ctx.ndk_dir, 'sources', 'openssl')
- for version in os.listdir(openssl_dir):
- mk_path = join(openssl_dir, version, 'Android.mk')
- if os.path.exists(mk_path):
- return mk_path
-
- def find_sqlite3_Android_mk(self):
- sqlite_dir = join(self.ctx.ndk_dir, 'sources', 'sqlite')
- for version in os.listdir(sqlite_dir):
- mk_path = join(sqlite_dir, version, 'Android.mk')
- if os.path.exists(mk_path):
- return mk_path
-
- def prebuild_arch(self, arch):
- super(Python3Recipe, self).prebuild_arch(arch)
- if self.version in ('3.6', '3.7', '3.9'):
- Python3Recipe.patches = [
- 'patch/patch_python3.6.patch',
- 'patch/remove_android_api_check.patch',
- 'patch/selectors.patch'
- ]
-
- if self.version in ('3.9'):
- Python3Recipe.patches = [
- 'patch/remove_android_api_check.patch',
- 'patch/patch_python3.9.patch',
- 'patch/platlibdir.patch',
- 'patch/strdup.patch',
-
- # from https://github.com/kivy/python-for-android/blob/develop/pythonforandroid/recipes/python3/__init__.py#L63
- 'patch/pyconfig_detection.patch',
- 'patch/reproducible-buildinfo.diff',
- 'patch/py3.8.1.patch'
- ]
-
- if sh.which('lld') is not None:
- Python3Recipe.patches += ['patch/py3.8.1_fix_cortex_a8.patch']
-
- build_dir = self.get_build_dir(arch.arch)
-
- # copy bundled libffi to _ctypes
- sh.cp("-r", join(self.get_recipe_dir(), 'libffi'), join(build_dir, 'Modules', '_ctypes'))
- print #####Copied bundle####'
-
- shprint(sh.ln, '-sf',
- realpath(join(build_dir, 'Lib/site-packages/README.txt')),
- join(build_dir, 'Lib/site-packages/README'))
- python_build_files = ['android.mk', 'config.c', 'interpreter.c']
- ndk_build_tools_python_dir = join(self.ctx.ndk_dir, 'build', 'tools', 'build-target-python')
- for python_build_file in python_build_files:
- shprint(sh.cp, join(self.get_recipe_dir(), '{}.{}'.format(python_build_file, self.version)),
- join(ndk_build_tools_python_dir, '{}.{}'.format(python_build_file, self.version)))
- ndk_sources_python_dir = join(self.ctx.ndk_dir, 'sources', 'python')
- if not os.path.exists(join(ndk_sources_python_dir, self.version)):
- os.mkdir(join(ndk_sources_python_dir, self.version))
- sh.sed('s#3.5#{}#'.format(self.version),
- join(ndk_sources_python_dir, '3.5/Android.mk'),
- _out=join(ndk_sources_python_dir, '{}/Android.mk.tmp'.format(self.version)))
- sh.sed('s#{}m#{}#'.format(self.version, self.version),
- join(ndk_sources_python_dir, '{}/Android.mk.tmp'.format(self.version)),
- _out=join(ndk_sources_python_dir, '{}/Android.mk'.format(self.version)))
- shprint(sh.rm, '-f', join(ndk_sources_python_dir, '{}/Android.mk.tmp'.format(self.version)))
-
- def build_arch(self, arch):
- rebuild = False
-
- if self.from_crystax and 'sqlite3' in self.ctx.recipe_build_order:
- info('checking sqlite3 in crystax-python')
- sqlite_recipe = self.get_recipe('sqlite3', self.ctx)
- stage, msg = self.check_for_sqlite3so(sqlite_recipe, arch)
- major_version = sqlite_recipe.version.split('.')[0]
- info(msg)
- sqlite3_build_dir = sqlite_recipe.get_build_dir(arch.arch)
- sqlite3_ndk_dir = join(self.ctx.ndk_dir, 'sources', 'sqlite', major_version)
-
- if stage < 2:
- info('copying sqlite3 Android.mk to ndk')
- ensure_dir(sqlite3_ndk_dir)
- if stage < 1.2:
- # copy include folder and Android.mk to ndk
- mk_path = self.find_sqlite3_Android_mk()
- if mk_path is None:
- raise IOError('Android.mk file could not be found in '
- 'any versions in ndk->sources->sqlite')
- if not ssl_recipe.version in mk_path:
- shprint(sh.cp, '-f', mk_path, sqlite3_ndk_dir)
-
- include_dir = join(sqlite3_build_dir, 'include')
- if stage < 1.3:
- ndk_include_dir = join(sqlite3_ndk_dir, 'include')
- ensure_dir(ndk_include_dir)
- shprint(sh.cp, '-f', join(sqlite3_build_dir, 'sqlite3.h'), join(ndk_include_dir, 'sqlite3.h'))
- shprint(sh.cp, '-f', join(sqlite3_build_dir, 'sqlite3ext.h'), join(ndk_include_dir, 'sqlite3ext.h'))
-
- if stage < 3:
- info('copying sqlite3 libs to ndk')
- arch_ndk_lib = join(sqlite3_ndk_dir, 'libs', arch.arch)
- ensure_dir(arch_ndk_lib)
- shprint(sh.ln, '-sf',
- realpath(join(sqlite3_build_dir, 'libsqlite3')),
- join(sqlite3_build_dir, 'libsqlite3.a'))
- libs = ['libs/{}/libsqlite3.a'.format(arch.arch)]
- cmd = [join(sqlite3_build_dir, lib) for lib in libs] + [arch_ndk_lib]
- shprint(sh.cp, '-f', *cmd)
-
- if stage < 10:
- rebuild = True
-
- # If openssl is needed we may have to recompile cPython to get the
- # ssl.py module working properly
- if self.from_crystax and 'openssl' in self.ctx.recipe_build_order:
- info('openssl and crystax-python combination may require '
- 'recompilation of python...')
- ssl_recipe = self.get_recipe('openssl', self.ctx)
- stage, msg = self.check_for_sslso(ssl_recipe, arch)
- stage = 0 if stage < 5 else stage
- info(msg)
- openssl_build_dir = ssl_recipe.get_build_dir(arch.arch)
- openssl_ndk_dir = join(self.ctx.ndk_dir, 'sources', 'openssl', ssl_recipe.version)
-
- if stage < 2:
- info('Copying openssl headers and Android.mk to ndk')
- ensure_dir(openssl_ndk_dir)
- if stage < 1.2:
- # copy include folder and Android.mk to ndk
- mk_path = self.find_Android_mk()
- if mk_path is None:
- raise IOError('Android.mk file could not be found in '
- 'any versions in ndk->sources->openssl')
- shprint(sh.cp, mk_path, openssl_ndk_dir)
-
- include_dir = join(openssl_build_dir, 'include')
- if stage < 1.3:
- ndk_include_dir = join(openssl_ndk_dir, 'include', 'openssl')
- self.copy_include_dir(join(include_dir, 'openssl'), ndk_include_dir)
-
- target_conf = join(openssl_ndk_dir, 'include', 'openssl',
- 'opensslconf.h')
- shprint(sh.rm, '-f', target_conf)
- # overwrite opensslconf.h
- with open(target_conf, 'w') as fp:
- fp.write(OPENSSLCONF)
-
- if stage < 1.4:
- # move current conf to arch specific conf in ndk
- under_scored_arch = arch.arch.replace('-', '_')
- shprint(sh.ln, '-sf',
- realpath(join(include_dir, 'openssl', 'opensslconf.h')),
- join(openssl_ndk_dir, 'include', 'openssl',
- 'opensslconf_{}.h'.format(under_scored_arch))
- )
-
- if stage < 3:
- info('Copying openssl libs to ndk')
- arch_ndk_lib = join(openssl_ndk_dir, 'libs', arch.arch)
- ensure_dir(arch_ndk_lib)
- shprint(sh.ln, '-sf',
- realpath(join(openssl_build_dir, 'libcrypto{}.so'.format(ssl_recipe.version))),
- join(openssl_build_dir, 'libcrypto.so'))
- shprint(sh.ln, '-sf',
- realpath(join(openssl_build_dir, 'libssl{}.so'.format(ssl_recipe.version))),
- join(openssl_build_dir, 'libssl.so'))
- libs = ['libcrypto.a', 'libcrypto.so', 'libssl.a', 'libssl.so']
- cmd = [join(openssl_build_dir, lib) for lib in libs] + [arch_ndk_lib]
- shprint(sh.cp, '-f', *cmd)
-
- if stage < 10:
- rebuild = True
-
- if rebuild:
- info('Recompiling python-crystax')
- self.patch_dev_defaults(ssl_recipe)
- build_script = join(self.ctx.ndk_dir, 'build', 'tools',
- 'build-target-python.sh')
-
- shprint(Command(build_script),
- '--ndk-dir={}'.format(self.ctx.ndk_dir),
- '--abis={}'.format(arch.arch),
- '-j5', '--verbose',
- self.get_build_dir(arch.arch))
-
- info('Extracting CrystaX python3 from NDK package')
- dirn = self.ctx.get_python_install_dir()
- ensure_dir(dirn)
- self.ctx.hostpython = 'python{}'.format(self.version)
-
-recipe = Python3Recipe()
diff --git a/recipes/python3crystax/android.mk.3.9 b/recipes/python3crystax/android.mk.3.9
deleted file mode 100644
index 72953a4..0000000
--- a/recipes/python3crystax/android.mk.3.9
+++ /dev/null
@@ -1,201 +0,0 @@
-LOCAL_SRC_FILES := config.c \
- $(MY_PYTHON_SRC_ROOT)/Python/asdl.c \
- $(MY_PYTHON_SRC_ROOT)/Python/ast.c \
- $(MY_PYTHON_SRC_ROOT)/Python/ast_opt.c \
- $(MY_PYTHON_SRC_ROOT)/Python/ast_unparse.c \
- $(MY_PYTHON_SRC_ROOT)/Python/bltinmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Python/bootstrap_hash.c \
- $(MY_PYTHON_SRC_ROOT)/Python/ceval.c \
- $(MY_PYTHON_SRC_ROOT)/Python/codecs.c \
- $(MY_PYTHON_SRC_ROOT)/Python/compile.c \
- $(MY_PYTHON_SRC_ROOT)/Python/context.c \
- $(MY_PYTHON_SRC_ROOT)/Python/dtoa.c \
- $(MY_PYTHON_SRC_ROOT)/Python/dup2.c \
- $(MY_PYTHON_SRC_ROOT)/Python/dynamic_annotations.c \
- $(MY_PYTHON_SRC_ROOT)/Python/dynload_shlib.c \
- $(MY_PYTHON_SRC_ROOT)/Python/errors.c \
- $(MY_PYTHON_SRC_ROOT)/Python/fileutils.c \
- $(MY_PYTHON_SRC_ROOT)/Python/formatter_unicode.c \
- $(MY_PYTHON_SRC_ROOT)/Python/frozen.c \
- $(MY_PYTHON_SRC_ROOT)/Python/frozenmain.c \
- $(MY_PYTHON_SRC_ROOT)/Python/future.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getargs.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getcompiler.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getcopyright.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getopt.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getplatform.c \
- $(MY_PYTHON_SRC_ROOT)/Python/getversion.c \
- $(MY_PYTHON_SRC_ROOT)/Python/graminit.c \
- $(MY_PYTHON_SRC_ROOT)/Python/hamt.c \
- $(MY_PYTHON_SRC_ROOT)/Python/hashtable.c \
- $(MY_PYTHON_SRC_ROOT)/Python/import.c \
- $(MY_PYTHON_SRC_ROOT)/Python/importdl.c \
- $(MY_PYTHON_SRC_ROOT)/Python/initconfig.c \
- $(MY_PYTHON_SRC_ROOT)/Python/marshal.c \
- $(MY_PYTHON_SRC_ROOT)/Python/modsupport.c \
- $(MY_PYTHON_SRC_ROOT)/Python/mysnprintf.c \
- $(MY_PYTHON_SRC_ROOT)/Python/mystrtoul.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pathconfig.c \
- $(MY_PYTHON_SRC_ROOT)/Python/peephole.c \
- $(MY_PYTHON_SRC_ROOT)/Python/preconfig.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pyarena.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pyctype.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pyfpe.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pyhash.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pylifecycle.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pymath.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pystate.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pystrcmp.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pystrhex.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pystrtod.c \
- $(MY_PYTHON_SRC_ROOT)/Python/Python-ast.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pythonrun.c \
- $(MY_PYTHON_SRC_ROOT)/Python/pytime.c \
- $(MY_PYTHON_SRC_ROOT)/Python/strdup.c \
- $(MY_PYTHON_SRC_ROOT)/Python/structmember.c \
- $(MY_PYTHON_SRC_ROOT)/Python/symtable.c \
- $(MY_PYTHON_SRC_ROOT)/Python/sysmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Python/thread.c \
- $(MY_PYTHON_SRC_ROOT)/Python/traceback.c \
- $(MY_PYTHON_SRC_ROOT)/Python/_warnings.c \
- \
- $(MY_PYTHON_SRC_ROOT)/Parser/acceler.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/grammar1.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/listnode.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/myreadline.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/node.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/parser.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/parsetok.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/token.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/tokenizer.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/pegen/parse.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/pegen/parse_string.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/pegen/peg_api.c \
- $(MY_PYTHON_SRC_ROOT)/Parser/pegen/pegen.c \
- \
- $(MY_PYTHON_SRC_ROOT)/Objects/abstract.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/accu.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/boolobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/bytearrayobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/bytes_methods.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/bytesobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/call.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/capsule.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/cellobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/classobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/codeobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/complexobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/descrobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/dictobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/enumobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/exceptions.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/fileobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/floatobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/frameobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/funcobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/genobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/genericaliasobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/interpreteridobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/iterobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/listobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/longobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/memoryobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/methodobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/moduleobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/namespaceobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/object.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/obmalloc.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/odictobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/picklebufobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/rangeobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/setobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/sliceobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/structseq.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/tupleobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/typeobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/unicodectype.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/unicodeobject.c \
- $(MY_PYTHON_SRC_ROOT)/Objects/weakrefobject.c \
- \
- $(MY_PYTHON_SRC_ROOT)/Modules/_abc.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/arraymodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_asynciomodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/atexitmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/audioop.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/binascii.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_bisectmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_bz2module.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cmathmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_codecsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_collectionsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_contextvarsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_csv.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_datetimemodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/errnomodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/faulthandler.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/fcntlmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_functoolsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/gcmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/getbuildinfo.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/getpath.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_heapqmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/itertoolsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_localemodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_lsprof.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/main.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_math.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/mathmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/md5module.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/mmapmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_opcode.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_operator.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/ossaudiodev.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/parsermodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_peg_parser.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/posixmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_posixsubprocess.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/pwdmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_queuemodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_randommodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/resource.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/rotatingtree.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/selectmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/sha1module.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/sha256module.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/sha512module.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/signalmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/socketmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_sre.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_stat.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_statisticsmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_struct.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/symtablemodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/termios.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_testbuffer.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_testimportmultiple.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_testmultiphase.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_threadmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/timemodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_tracemalloc.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/unicodedata.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_weakref.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/xxlimited.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/xxmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_xxsubinterpretersmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/xxsubtype.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/zlibmodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_zoneinfo.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_cn.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_hk.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_iso2022.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_jp.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_kr.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/_codecs_tw.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/cjkcodecs/multibytecodec.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/_iomodule.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/textio.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/iobase.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/bufferedio.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/stringio.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/bytesio.c \
- $(MY_PYTHON_SRC_ROOT)/Modules/_io/fileio.c
diff --git a/recipes/python3crystax/config.c.3.9 b/recipes/python3crystax/config.c.3.9
deleted file mode 100644
index 98ea8f2..0000000
--- a/recipes/python3crystax/config.c.3.9
+++ /dev/null
@@ -1,138 +0,0 @@
-#include
-
-extern PyObject* PyInit_posix(void);
-extern PyObject* PyInit_pwd(void);
-extern PyObject* PyInit_fcntl(void);
-extern PyObject* PyInit__posixsubprocess(void);
-extern PyObject* PyInit_termios(void);
-extern PyObject* PyInit_audioop(void);
-extern PyObject* PyInit__locale(void);
-extern PyObject* PyInit_array(void);
-extern PyObject* PyInit_binascii(void);
-extern PyObject* PyInit_cmath(void);
-extern PyObject* PyInit_errno(void);
-extern PyObject* PyInit_faulthandler(void);
-extern PyObject* PyInit__tracemalloc(void);
-extern PyObject* PyInit_gc(void);
-extern PyObject* PyInit_math(void);
-extern PyObject* PyInit__md5(void);
-extern PyObject* PyInit__operator(void);
-extern PyObject* PyInit__signal(void);
-extern PyObject* PyInit__sha1(void);
-extern PyObject* PyInit__sha256(void);
-extern PyObject* PyInit__sha512(void);
-extern PyObject* PyInit_time(void);
-extern PyObject* PyInit__thread(void);
-extern PyObject* PyInit__codecs(void);
-extern PyObject* PyInit__weakref(void);
-extern PyObject* PyInit_xxsubtype(void);
-extern PyObject* PyInit__xxsubinterpreters(void);
-extern PyObject* PyInit__random(void);
-extern PyObject* PyInit_itertools(void);
-extern PyObject* PyInit__collections(void);
-extern PyObject* PyInit__heapq(void);
-extern PyObject* PyInit__bisect(void);
-extern PyObject* PyInit__symtable(void);
-extern PyObject* PyInit_mmap(void);
-extern PyObject* PyInit__csv(void);
-extern PyObject* PyInit__sre(void);
-extern PyObject* PyInit_parser(void);
-extern PyObject* PyInit__struct(void);
-extern PyObject* PyInit__datetime(void);
-extern PyObject* PyInit__functools(void);
-extern PyObject* PyInit_zlib(void);
-extern PyObject* PyInit__multibytecodec(void);
-extern PyObject* PyInit__codecs_cn(void);
-extern PyObject* PyInit__codecs_hk(void);
-extern PyObject* PyInit__codecs_iso2022(void);
-extern PyObject* PyInit__codecs_jp(void);
-extern PyObject* PyInit__codecs_kr(void);
-extern PyObject* PyInit__codecs_tw(void);
-extern PyObject* PyInit__winapi(void);
-extern PyObject* PyInit__lsprof(void);
-extern PyObject* PyInit__ast(void);
-extern PyObject* PyInit__io(void);
-extern PyObject* PyInit_atexit(void);
-extern PyObject* _PyWarnings_Init(void);
-extern PyObject* PyInit__string(void);
-extern PyObject* PyInit__stat(void);
-extern PyObject* PyInit__opcode(void);
-extern PyObject* PyMarshal_Init(void);
-extern PyObject* PyInit__imp(void);
-
-struct _inittab _PyImport_Inittab[] = {
- {"posix", PyInit_posix},
- {"pwd", PyInit_pwd},
- {"fcntl", PyInit_fcntl},
- {"_posixsubprocess", PyInit__posixsubprocess},
- {"termios", PyInit_termios},
- {"audioop", PyInit_audioop},
- {"_locale", PyInit__locale},
- {"array", PyInit_array},
- {"_ast", PyInit__ast},
- {"binascii", PyInit_binascii},
- {"cmath", PyInit_cmath},
- {"errno", PyInit_errno},
- {"faulthandler", PyInit_faulthandler},
- {"gc", PyInit_gc},
- {"math", PyInit_math},
- {"_operator", PyInit__operator},
- {"_signal", PyInit__signal},
- {"_md5", PyInit__md5},
- {"_sha1", PyInit__sha1},
- {"_sha256", PyInit__sha256},
- {"_sha512", PyInit__sha512},
- {"time", PyInit_time},
- {"_thread", PyInit__thread},
- {"_tracemalloc", PyInit__tracemalloc},
- {"_codecs", PyInit__codecs},
- {"_weakref", PyInit__weakref},
- {"_random", PyInit__random},
- {"_bisect", PyInit__bisect},
- {"_heapq", PyInit__heapq},
- {"_lsprof", PyInit__lsprof},
- {"itertools", PyInit_itertools},
- {"_collections", PyInit__collections},
- {"_symtable", PyInit__symtable},
- {"mmap", PyInit_mmap},
- {"_csv", PyInit__csv},
- {"_sre", PyInit__sre},
- {"parser", PyInit_parser},
- {"_struct", PyInit__struct},
- {"_datetime", PyInit__datetime},
- {"_functools", PyInit__functools},
-
- {"xxsubtype", PyInit_xxsubtype},
- {"_xxsubinterpreters", PyInit__xxsubinterpreters},
- {"zlib", PyInit_zlib},
-
- /* CJK codecs */
- {"_multibytecodec", PyInit__multibytecodec},
- {"_codecs_cn", PyInit__codecs_cn},
- {"_codecs_hk", PyInit__codecs_hk},
- {"_codecs_iso2022", PyInit__codecs_iso2022},
- {"_codecs_jp", PyInit__codecs_jp},
- {"_codecs_kr", PyInit__codecs_kr},
- {"_codecs_tw", PyInit__codecs_tw},
-
- /* This module "lives in" with marshal.c */
- {"marshal", PyMarshal_Init},
-
- /* This lives it with import.c */
- {"_imp", PyInit__imp},
-
- /* These entries are here for sys.builtin_module_names */
- {"builtins", NULL},
- {"sys", NULL},
- {"_warnings", _PyWarnings_Init},
- {"_string", PyInit__string},
-
- {"_io", PyInit__io},
- {"atexit", PyInit_atexit},
- {"_stat", PyInit__stat},
- {"_opcode", PyInit__opcode},
-
- /* Sentinel */
- {0, 0}
-};
-
diff --git a/recipes/python3crystax/interpreter.c.3.9 b/recipes/python3crystax/interpreter.c.3.9
deleted file mode 100644
index c914e97..0000000
--- a/recipes/python3crystax/interpreter.c.3.9
+++ /dev/null
@@ -1,199 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#define PYTHON3_STDLIB_REL_PATH "stdlib.zip"
-#define PYTHON3_MODULES_REL_PATH "modules"
-#define PYTHON3_DLL_REL_PATH "libpython3.9.so"
-
-#define SYS_PATH_BUFFER_SIZE (2*(PATH_MAX + 1))
-
-static char NULL_PTR_STR[] = "NULL";
-
-static void GetExecutablePath(char* path)
-{
- int size = readlink("/proc/self/exe", path, PATH_MAX);
- if (size < 0)
- size = 0;
- path[size] = 0;
-}
-
-static void GetRelativePathFormat(char* base, char* fmt)
-{
- unsigned idx;
- char *p, *end;
- end = strrchr(base, '/');
- for (idx = 0, p = base; *p; ++p, ++idx)
- {
- fmt[idx] = *p;
- if (p == end)
- break;
- }
- fmt[++idx] = '%';
- fmt[++idx] = 's';
- fmt[++idx] = 0;
-}
-
-typedef void (*Py_SetProgramNamePtr)(wchar_t*);
-typedef void (*Py_SetPathPtr)(const wchar_t*);
-typedef int (*Py_MainPtr)(int, wchar_t**);
-typedef void* (*PyMem_RawMallocPtr)(size_t);
-typedef void (*PyMem_RawFreePtr)(void*);
-typedef wchar_t* (*Py_DecodeLocalePtr)(const char*, size_t*);
-
-
-int main(int argc, char** argv)
-{
- char executable[PATH_MAX + 1] = {0};
- char pthfmt[PATH_MAX + 1] = {0};
- char corepath[PATH_MAX + 1] = {0};
- char stdlibpath[PATH_MAX + 1] = {0};
- char modpath[PATH_MAX + 1] = {0};
- char syspath[SYS_PATH_BUFFER_SIZE] = {0};
- void* core = 0;
- int retcode = 126;
- int i;
-
- Py_SetProgramNamePtr Py_SetProgramName = 0;
- Py_SetPathPtr Py_SetPath = 0;
- Py_MainPtr Py_Main = 0;
- PyMem_RawMallocPtr PyMem_RawMalloc = 0;
- PyMem_RawFreePtr PyMem_RawFree = 0;
- Py_DecodeLocalePtr Py_DecodeLocale = 0;
-
- GetExecutablePath(executable);
- GetRelativePathFormat(executable, pthfmt);
-
- snprintf(corepath, PATH_MAX, pthfmt, PYTHON3_DLL_REL_PATH);
- snprintf(stdlibpath, PATH_MAX, pthfmt, PYTHON3_STDLIB_REL_PATH);
- snprintf(modpath, PATH_MAX, pthfmt, PYTHON3_MODULES_REL_PATH);
- snprintf(syspath, SYS_PATH_BUFFER_SIZE-1, "%s:%s", stdlibpath, modpath);
-
- core = dlopen(corepath, RTLD_LAZY);
- if (core == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load library: '%s', dlerror: %s\n", corepath, lasterr);
- goto exit;
- }
-
- Py_SetProgramName = (Py_SetProgramNamePtr)dlsym(core, "Py_SetProgramName");
- if (Py_SetProgramName == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_SetProgramName", corepath, lasterr);
- goto exit;
- }
-
- Py_SetPath = (Py_SetPathPtr)dlsym(core, "Py_SetPath");
- if (Py_SetPath == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_SetPath", corepath, lasterr);
- goto exit;
- }
-
- Py_Main = (Py_MainPtr)dlsym(core, "Py_Main");
- if (Py_Main == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_Main", corepath, lasterr);
- goto exit;
- }
-
- PyMem_RawMalloc = (PyMem_RawMallocPtr)dlsym(core, "PyMem_RawMalloc");
- if (PyMem_RawMalloc == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "PyMem_RawMalloc", corepath, lasterr);
- goto exit;
- }
-
- PyMem_RawFree = (PyMem_RawFreePtr)dlsym(core, "PyMem_RawFree");
- if (PyMem_RawFree == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "PyMem_RawFree", corepath, lasterr);
- goto exit;
- }
-
- Py_DecodeLocale = (Py_DecodeLocalePtr)dlsym(core, "Py_DecodeLocale");
- if (Py_DecodeLocale == 0)
- {
- const char* lasterr = dlerror();
- if (lasterr == 0)
- lasterr = NULL_PTR_STR;
- fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_DecodeLocale", corepath, lasterr);
- goto exit;
- }
-
- wchar_t* executable_w = Py_DecodeLocale(executable, 0);
- if (executable_w == 0)
- {
- fprintf(stderr, "Fatal Python error: unable to decode executable path: '%s'\n", executable);
- goto exit;
- }
-
- wchar_t* syspath_w = Py_DecodeLocale(syspath, 0);
- if (syspath_w == 0)
- {
- fprintf(stderr, "Fatal Python error: unable to decode syspath: '%s'\n", syspath);
- goto exit;
- }
-
- wchar_t** argv_copy = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*)*(argc+1));
- wchar_t** argv_copy2 = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*)*(argc+1));
-
- char* oldloc = strdup(setlocale(LC_ALL, 0));
- setlocale(LC_ALL, "");
- for (i = 0; i < argc; ++i)
- {
- argv_copy[i] = Py_DecodeLocale(argv[i], 0);
- if (argv_copy[i] == 0)
- {
- free(oldloc);
- fprintf(stderr, "Fatal Python error: unable to decode the command line argument #%i\n", i + 1);
- goto exit;
- }
- argv_copy2[i] = argv_copy[i];
- }
- argv_copy2[argc] = argv_copy[argc] = 0;
- setlocale(LC_ALL, oldloc);
- free(oldloc);
-
- Py_SetProgramName(executable_w);
- Py_SetPath(syspath_w);
- retcode = Py_Main(argc, argv_copy);
-
- PyMem_RawFree(executable_w);
- PyMem_RawFree(syspath_w);
- for (i = 0; i < argc; i++)
- {
- PyMem_RawFree(argv_copy2[i]);
- }
- PyMem_RawFree(argv_copy);
- PyMem_RawFree(argv_copy2);
-
-exit:
- if (core != 0)
- dlclose(core);
-
- return retcode;
-}
diff --git a/recipes/python3crystax/libffi/ChangeLog b/recipes/python3crystax/libffi/ChangeLog
deleted file mode 100644
index c85cbe6..0000000
--- a/recipes/python3crystax/libffi/ChangeLog
+++ /dev/null
@@ -1,5105 +0,0 @@
-commit 0403f332b1f478696c30d3d8a0e2f6eef24aaf88
-Author: Anthony Green
-Date: Mon May 19 09:41:32 2014 -0400
-
- Update date. Annoucing 3.1 today.
-
-commit 94ac0c168ee7b115409121d88b25a4979446c8da
-Author: Anthony Green
-Date: Mon May 19 09:37:21 2014 -0400
-
- Increment libtool library revision number
-
-commit 57465744b6e1295d7202de5a7734df589518f1c8
-Author: Anthony Green
-Date: Sun May 11 10:30:22 2014 -0400
-
- Update to version 3.1
-
-commit 0c2251a42df5108b6d9ebe5fe1cf83d0bcdf660e
-Author: Anthony Green
-Date: Sun May 11 10:22:30 2014 -0400
-
- Support versions of git older than 1.8.5
-
-commit 70c303cb88e23aaee91c87c56b108c50ab4f3c2f
-Author: Anthony Green
-Date: Sun May 11 09:56:40 2014 -0400
-
- Fix testsuite for GCC 4.9.0
-
-commit 52b3457093ed19b2a7c5fcf243c4014c90ce6225
-Author: Magnus Granberg
-Date: Sun May 11 09:55:28 2014 -0400
-
- Check /proc/self/status for PaX status.
-
-commit 7ba4c5d72aa440a4b21fb57e999e67c5957761da
-Author: Dominik Vogt
-Date: Sun May 11 09:52:47 2014 -0400
-
- Use to get correct dir
-
-commit 31e0d4ecff6dc2a6c75a066ee099b52a43f6ba27
-Merge: 1c0e9a7 99909eb
-Author: Anthony Green
-Date: Wed Apr 23 19:24:47 2014 -0400
-
- Merge pull request #119 from joshtriplett/fastcall-fastball
-
- src/x86/win32.S: Define ffi_closure_FASTCALL in the MASM section, too
-
-commit 99909eb6184b62408d88b6b4e7ab38e84e6d0bf3
-Author: Josh Triplett
-Date: Tue Apr 22 21:17:52 2014 -0700
-
- src/x86/win32.S: Define ffi_closure_FASTCALL in the MASM section, too
-
-commit 1c0e9a7297ced15413c2d2d5d35f6c650c4b46c9
-Merge: 93a24f2 d369522
-Author: Anthony Green
-Date: Mon Apr 21 12:41:56 2014 -0400
-
- Merge pull request #101 from joshtriplett/fastcall-closures
-
- Support closures for fastcall
-
-commit d36952273d4fafbda91ecc205fc0824f7cc65e70
-Author: Josh Triplett
-Date: Sun Apr 20 12:03:25 2014 -0700
-
- Support fastcall closures
-
- libffi on 32-bit x86 now supports closures for all supported ABIs.
- Thus, rewrite the last remaining duplicated-by-ABI test (closure_stdcall
- and closure_thiscall) to use the generic ABI_NUM/ABI_ATTR mechanism.
-
-commit 93a24f216bcdd1018b976d697179c6d49004015a
-Merge: dd11a04 2349fec
-Author: Anthony Green
-Date: Sat Apr 12 19:38:07 2014 -0400
-
- Merge pull request #80 from ueno/devel
-
- Fix typo in doc
-
-commit dd11a04061cb49ce1d702545693c24eb1267d648
-Merge: 8fa2812 03ca880
-Author: Anthony Green
-Date: Sat Apr 12 19:37:21 2014 -0400
-
- Merge pull request #86 from joshtriplett/testsuite-CC-CXX
-
- testsuite ignores CC parameter supplied to configure or make
-
-commit 8fa2812355e685a42abf9a62fbc674d616b2edee
-Merge: 8a58e6b 419503f
-Author: Anthony Green
-Date: Sat Apr 12 19:32:08 2014 -0400
-
- Merge pull request #116 from frida/fix/darwin-aarch64-variadic
-
- Fix handling of variadic calls on Darwin/AArch64
-
-commit 8a58e6b7805b736def197b8baf8e465a2a3f6913
-Merge: 30b77c5 a539f7f
-Author: Anthony Green
-Date: Sat Apr 12 19:30:18 2014 -0400
-
- Merge pull request #115 from frida/fix/darwin-aarch64-alignment
-
- Fix alignment of AArch64 assembler functions
-
-commit 30b77c56f95c63ecd83399aafdbad7b07330f2fd
-Merge: dc33cb3 3e2b84d
-Author: Anthony Green
-Date: Sat Apr 12 19:29:13 2014 -0400
-
- Merge pull request #117 from frida/fix/windows-regression
-
- Fix Windows regression
-
-commit 3e2b84d295531720917bf46afc532fc6d877e3ec
-Author: Ole André Vadla Ravnås
-Date: Sat Apr 12 01:04:04 2014 +0200
-
- Fix Windows regression
-
- Introduced by b5fed601948237037513a9b7f967c8fc6c9ff1f6.
-
-commit 419503f409c321fe31ff59d963ef34bb913420d0
-Author: Ole André Vadla Ravnås
-Date: Sun Apr 6 20:54:13 2014 +0200
-
- Fix handling of variadic calls on Darwin/AArch64
-
-commit a539f7ffd6783aa11353d13265520e453c565fb4
-Author: Ole André Vadla Ravnås
-Date: Sun Apr 6 20:53:02 2014 +0200
-
- Fix alignment of AArch64 assembler functions
-
-commit dc33cb3c998da521a960385c1269c3aef552f69f
-Merge: c860a99 b5fed60
-Author: Anthony Green
-Date: Sat Apr 5 23:41:22 2014 -0400
-
- Merge pull request #114 from joshtriplett/bounce-on-a-tiny-trampoline
-
- Fix ABI on 32-bit non-Windows x86: go back to trampoline size 10
-
-commit b5fed601948237037513a9b7f967c8fc6c9ff1f6
-Author: Josh Triplett
-Date: Sat Apr 5 17:33:42 2014 -0700
-
- Fix ABI on 32-bit non-Windows x86: go back to trampoline size 10
-
- The trampoline size is part of the ABI, so it cannot change. Move the
- logic from the stdcall and thiscall trampolines to the functions they
- call, to reduce them both to 10 bytes.
-
- This drops the previously added support for raw THISCALL closures on
- non-Windows. (Non-raw THISCALL closures still work.)
-
-commit 03ca880081b22efab09ba72268270f83017d3d7b
-Author: Josh Triplett
-Date: Thu Mar 27 08:44:34 2014 -0700
-
- README: Note the testsuite changes to respect $CC and $CXX
-
-commit d74df8c5d8c6722ecb908da98c86cc8e2c755b84
-Author: Josh Triplett
-Date: Thu Mar 27 00:44:12 2014 -0700
-
- README: Update Windows example to set both CC and CXX
-
-commit 7d698125b1f05173f3656a89755a2eb58813b002
-Author: Josh Triplett
-Date: Wed Mar 26 23:17:56 2014 -0700
-
- Use the proper C++ compiler to run C++ tests
-
- Running the C compiler with -shared-libgcc -lstdc++ does not work on
- non-GCC compilers.
-
-commit fa5e88f170cb37c7b2b9bb015c8c5b854ffd8a3e
-Author: Josh Triplett
-Date: Wed Mar 26 23:53:57 2014 -0700
-
- .travis.yml: Make the build command more readable by splitting at &&
-
- "script" can contain multiple commands to run in sequence.
-
-commit 0c3824702d3d59d37f8c177d646303f546187683
-Author: Josh Triplett
-Date: Wed Mar 26 14:51:32 2014 -0700
-
- Always set CC_FOR_TARGET for dejagnu, to make the testsuite respect $CC
-
- This fixes cross-compilation and compilation with CC="gcc -m32".
-
-commit 9946a92af31b30cb7760150d1f8ca6c11b01aeea
-Author: Josh Triplett
-Date: Wed Mar 26 20:18:58 2014 -0700
-
- Stop looking for expect and runtest above top_builddir
-
- Users wishing to test hand-compiled versions of expect and runtest can
- easily enough put them in their path or set EXPECT and RUNTEST
- themselves.
-
-commit acb202325215058639234efb7af1f04c1c8a1f44
-Author: Josh Triplett
-Date: Wed Mar 26 20:18:41 2014 -0700
-
- Stop setting an empty AM_RUNTESTFLAGS
-
-commit c860a992fef5d7cd7bb0975b1632d17a9fafe007
-Author: Anthony Green
-Date: Tue Mar 25 17:02:51 2014 -0400
-
- Upgrade version to 3.1-rc1
-
-commit 9837073e6203048a162a226798c5d252600219ed
-Author: Anthony Green
-Date: Tue Mar 25 16:24:14 2014 -0400
-
- Update copyright date and clean up README notes.
-
-commit 18d3baa9f597b026675baa1b4e5a5eeef7577a08
-Merge: afee537 f0c8a31
-Author: Anthony Green
-Date: Tue Mar 25 16:12:53 2014 -0400
-
- Merge pull request #108 from joshtriplett/freebsd
-
- [3.1 blocker] Fix FreeBSD support
-
-commit afee53738a995e23bd2f89fd0f7b30b380566106
-Merge: 7d24785 b2d610e
-Author: Anthony Green
-Date: Tue Mar 25 16:12:35 2014 -0400
-
- Merge pull request #106 from joshtriplett/darwin-award
-
- [3.1 blocker] Update OS X build system to include win32.S on 32-bit
-
-commit 7d2478568ed9f03cbf57627f449a2d2cf4d1571c
-Merge: beab5f3 56be47f
-Author: Anthony Green
-Date: Tue Mar 25 16:12:17 2014 -0400
-
- Merge pull request #110 from joshtriplett/w64
-
- Fix 64-bit Windows support
-
-commit beab5f334d9ec5b8b91d1cc727d1029b40358e7e
-Merge: 28fb197 ef5890e
-Author: Anthony Green
-Date: Tue Mar 25 16:07:47 2014 -0400
-
- Merge pull request #105 from joshtriplett/win32-relocations
-
- [3.1 blocker] win32.S needs to handle relocations/GOT
-
-commit f0c8a31577172104049283f0a80c723084a5bd77
-Author: Josh Triplett
-Date: Mon Mar 24 22:14:26 2014 -0700
-
- Compile win32.S on FreeBSD
-
-commit b2d610e028b5ce48d1ad7e5d0debc9c321d891b2
-Author: Josh Triplett
-Date: Fri Mar 21 11:10:13 2014 -0700
-
- Compile win32.S on 32-bit Darwin as well
-
-commit be50b87a490e794362cb4a27ada2fbaab202adb8
-Author: Josh Triplett
-Date: Mon Mar 24 21:44:13 2014 -0700
-
- Always use configure to detect whether global symbols need underscores
-
- 64-bit Windows already used this check; make it universal, and use it in
- place of an ifdef on X86_WIN32, to handle non-Windows platforms that use
- the underscore, such as Darwin.
-
-commit 56be47f87629e31afbcb0774aa65735f539ee972
-Author: Josh Triplett
-Date: Mon Mar 24 21:24:53 2014 -0700
-
- Fix a warning on 64-bit Windows
-
- When sizeof(size_t) != sizeof(unsigned), adding a size_t to cif->bytes
- produces a "possible loss of data" warning. However, the size_t in
- question refers to the size of a single parameter. Use a cast to avoid
- the warning.
-
-commit 48a8eda74aad8a21b6f26df5df08fe64c043d208
-Author: Josh Triplett
-Date: Mon Mar 24 21:21:12 2014 -0700
-
- Avoid referencing undefined ABIs on 64-bit Windows builds
-
- 64-bit Windows does not have FFI_STDCALL, FFI_THISCALL, or FFI_FASTCALL.
-
-commit f0f4138f90345d7d67dfa6783a7e1c7cc30d3c6f
-Author: Josh Triplett
-Date: Sat Mar 22 10:00:53 2014 -0700
-
- win32.S: Add handling for position-independent code on Darwin
-
- Newer versions of Darwin generate the necessary stub functions
- automatically and just need a call instruction, but accomodating older
- versions as well requires adding the stub.
-
-commit ef5890ebafb7cd2fbf9acf161edb55fe1382871c
-Author: Josh Triplett
-Date: Fri Mar 21 11:01:39 2014 -0700
-
- win32.S: Use shifting for multiplication rather than repeated addition
-
- The jump table code added a register to itself twice to multiply by 4;
- shift the register left by 2 instead.
-
-commit 4fca48901e7e4f53bf490ed22607b2d2d8f4bfcc
-Author: Josh Triplett
-Date: Fri Mar 21 11:00:41 2014 -0700
-
- win32.S: Make the jump tables position-independent
-
- Now that non-Windows platforms include win32.S, it needs to support
- building as position-independent code. This fixes build failures on
- target platforms that do not allow text relocations.
-
-commit 2087dcf736274286f76c69d3988fb6d7cc4fd0f5
-Author: Josh Triplett
-Date: Fri Mar 21 10:57:06 2014 -0700
-
- win32.S: Make calls to ffi_closure_SYSV_inner position-independent
-
- Now that non-Windows platforms include win32.S, it needs to support
- building as position-independent code. This fixes one source of build
- failures on target platforms that do not allow text relocations.
-
-commit 28fb197079cf1d11da4eef7c8c243ab05590c528
-Merge: c697472 c3dd0a1
-Author: Anthony Green
-Date: Tue Mar 18 12:19:36 2014 -0400
-
- Merge pull request #107 from rvandermeulen/msvcc
-
- Various compatibility fixes and improvements to msvcc.sh.
-
-commit c3dd0a1a0245fc174361a70876e88ae24285f861
-Author: Ryan VanderMeulen
-Date: Tue Mar 18 12:09:45 2014 -0400
-
- Various compatibility fixes and improvements to msvcc.sh.
-
- * Don't try to mix incompatible optimization flags in debug builds.
- * Workaround ax_cc_maxopt.m4 not supporting MSVC and change -O3 to -O2.
- * Fix MSVC warning by properly passing linker flags to compiler.
- * Make msvcc.sh return 1 if invalid command line options are used rather than silently eating them.
- * Add more comments.
-
-commit c697472fccfbb5b87b007c053cda9ef014b346b9
-Merge: 83fd2bc e48918e
-Author: Anthony Green
-Date: Mon Mar 17 00:32:42 2014 -0400
-
- Merge pull request #102 from joshtriplett/test-generic
-
- Add ABIs to the test matrix; unify many bits of the testsuite
-
-commit e48918ecf876bc85d040fc50a232059c566553a8
-Author: Josh Triplett
-Date: Sun Mar 16 20:29:27 2014 -0700
-
- testsuite: Add ABIs to the test matrix; unify tests across ABIs
-
- This eliminates all the *_win32.c tests in favor of the tests they were
- branched from, and expands test coverage to run many more tests on
- stdcall, thiscall, and fastcall.
-
- This same mechanism also supports testing any other target that has
- multiple ABIs.
-
-commit 4d4d368e5a55d9443c4c53b1b70d58ab6d8c941c
-Author: Josh Triplett
-Date: Sun Mar 16 17:02:05 2014 -0700
-
- testsuite: Replace ffitestcxx.h with ffitest.h
-
- ffitest.h contains a superset of the functionality of ffitestcxx.h;
- make the C++ tests include ffitest.h instead, and remove ffitestcxx.h.
-
-commit 3f97cf3413c46caf2a79f32ac9cda4620972c2d7
-Author: Josh Triplett
-Date: Sun Mar 16 16:53:42 2014 -0700
-
- testsuite: Unify the C and C++ testsuites
-
- These two testsuites differ only in the source file glob and a couple of
- additional compiler options; unify the remaining bits.
-
-commit 0d9cce8edb937bbe771a6cdd25f671edf06d2128
-Author: Josh Triplett
-Date: Sun Mar 16 16:22:58 2014 -0700
-
- testsuite: ffitest.h: Parenthesize the CHECK macro
-
-commit 5695ec1444c5323e48fe4314f8c8f027625e67df
-Author: Josh Triplett
-Date: Sun Mar 16 16:04:58 2014 -0700
-
- testsuite: Factor out a function to run a matrix of tests
-
- This commons up code from libffi.call/call.exp and
- libffi.special/special.exp, unifies the optimization option matrix
- between the two, and makes it easier to add more axes to the matrix
- in the future.
-
-commit dfdb02cc869855d3b68571e5f7aa77ae8c9d254a
-Author: Josh Triplett
-Date: Sun Mar 16 15:26:26 2014 -0700
-
- testsuite: Introduce a __THISCALL__ compiler-specific macro
-
-commit 83fd2bce0456224483435d4b764063f4513fd464
-Merge: 3658a07 06ff924
-Author: Anthony Green
-Date: Sun Mar 16 22:03:29 2014 -0400
-
- Merge pull request #99 from joshtriplett/gitignore
-
- .gitignore: Ignore more generated files
-
-commit 3658a0700a50d37a2fdba04fd9d79ad2f706d9f5
-Merge: d948d0a 46c5d3c
-Author: Anthony Green
-Date: Sun Mar 16 21:37:42 2014 -0400
-
- Merge pull request #100 from rvandermeulen/bug-756740
-
- Change double quotes in Makefile.am to single quotes.
-
-commit 46c5d3c30fdc2b43c076ad955078d7c5f1e75b37
-Author: Ryan VanderMeulen
-Date: Sun Mar 16 21:16:08 2014 -0400
-
- Change double quotes in Makefile.am to single quotes.
-
- This was originally done in PR #84, except the change was made to Makefile.in instead of Makefile.am and was therefore reverted the next time the files were regenerated.
-
-commit 06ff924215a2f9739efa2c059dc595bc4ec1c851
-Author: Josh Triplett
-Date: Sun Mar 16 16:19:46 2014 -0700
-
- .gitignore: Ignore more generated files
-
- The build process generates doc/libffi.info and fficonfig.h.in, so add
- them to .gitignore.
-
-commit bad8948346e9b8813023a0cc78a3b6eb8d9c14c6
-Author: Josh Triplett
-Date: Sun Mar 16 15:16:18 2014 -0700
-
- testsuite: Introduce a __STDCALL__ compiler-specific macro
-
- Several tests want to use stdcall, which differs in syntax by compiler,
- so introduce a macro for it in ffitest.h.
-
-commit 98a793fa36a4ab3ba24d059cb80a2891cdb940e1
-Author: Josh Triplett
-Date: Sun Mar 16 15:20:36 2014 -0700
-
- testsuite: Common up the ifdef blocks for compiler-specific macros
-
-commit d948d0a729c934b0224749338a3ba0a2c8f51c45
-Merge: b61b472 a86bd31
-Author: Anthony Green
-Date: Sun Mar 16 10:53:48 2014 -0400
-
- Merge pull request #98 from joshtriplett/unconfigure.host
-
- Merge configure.host into configure.ac
-
-commit a86bd318e2424d879d784ee7b29d6536d7a17c18
-Author: Josh Triplett
-Date: Sun Mar 16 06:58:59 2014 -0700
-
- Merge configure.host into configure.ac
-
- configure.host only has a single entry, and shows no signs of needing
- more added.
-
-commit b61b472bd0647006d6685238721002017f1d119c
-Author: Anthony Green
-Date: Sun Mar 16 09:45:55 2014 -0400
-
- Update version to 3.1-rc0. Clean up README.
-
-commit 7a64e7dbba54e6e9f69954adfb943be1856ff928
-Merge: 11a5c5c eef2e02
-Author: Anthony Green
-Date: Sun Mar 16 09:39:08 2014 -0400
-
- Merge pull request #97 from joshtriplett/remove-more-generated-files
-
- Remove more generated files
-
-commit 11a5c5c39f5861011f6c5ddf795da3a32b5f0082
-Merge: 9a62a21 1c68c07
-Author: Anthony Green
-Date: Sun Mar 16 09:38:47 2014 -0400
-
- Merge pull request #96 from joshtriplett/sawing-changelogs
-
- Generate ChangeLog from git in make dist; remove it from version control
-
-commit eef2e02a13d7d1c8145d47a64467f654406a3548
-Author: Josh Triplett
-Date: Sun Mar 16 06:26:03 2014 -0700
-
- doc: Remove autogenerated info file and stamp
-
-commit 9fb403d3c5d9643e0f561cab6d4a07b1e54907ff
-Author: Josh Triplett
-Date: Sun Mar 16 06:25:52 2014 -0700
-
- fficonfig.h.in: Remove, configure generates it
-
-commit 1c68c07217fda78a779778c1480fedef7a58d5b4
-Author: Josh Triplett