Skip to content

Commit b38c6ae

Browse files
committed
Add libraries support for python 3
Introduces a simple mechanism to set cflags/ldflags for some optional libs that can be linked with python. This mechanism is created in order to keep the code clean and readable, and should make easier to add other optional libs. The supported libraries are: - sqlite3 - openssl - libexpat - libffi The libraries should be tested with a real app and further changes may be needed. Also add termios patch in order to avoid runtime error when running the app.
1 parent 5db62ac commit b38c6ae

File tree

3 files changed

+221
-16
lines changed

3 files changed

+221
-16
lines changed

pythonforandroid/recipes/python3/__init__.py

Lines changed: 145 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,136 @@
22
from pythonforandroid.toolchain import shprint, current_directory, info
33
from pythonforandroid.patching import (is_darwin, is_api_gt,
44
check_all, is_api_lt, is_ndk)
5-
from pythonforandroid.logger import logger
5+
from pythonforandroid.logger import (logger, Err_Fore)
66
from pythonforandroid.util import ensure_dir
77
from os.path import exists, join, realpath
88
from os import environ
99
import sh
1010

1111

12+
# Here we set data for python's optional libraries, any optional future library
13+
# should be referenced into variable `libs_info` and maybe in `versioned_libs`.
14+
# Perhaps some additional operations had to be performed in functions:
15+
# - set_libs_flags: if the path for library/includes is in some location
16+
# defined dynamically by some function.
17+
# - do_python_build: if python has some specific argument to enable
18+
# external library support.
19+
20+
# versioned_libs is a list with versioned libraries
21+
versioned_libs = ['openssl']
22+
23+
# libs_info is a dict where his keys are the optional recipes,
24+
# each value of libs_info is a dict:
25+
# - includes: list of includes (should be relative paths
26+
# which point to the includes).
27+
# - lib_links: list of ldflags needed to link python with the library.
28+
# - lib_path: relative path pointing to the library location,
29+
# (if is set to None, the library's build dir will be taken).
30+
libs_info = {
31+
'openssl': {
32+
'includes': ['include', join('include', 'openssl')],
33+
'lib_links': ['-lcrypto', '-lssl'],
34+
'lib_path': None,
35+
},
36+
'sqlite3': {
37+
'includes': [''],
38+
'lib_links': ['-lsqlite3'],
39+
'lib_path': None,
40+
},
41+
'libffi': {
42+
'includes': ['include'],
43+
'lib_links': ['-lffi'],
44+
'lib_path': '.libs',
45+
},
46+
'libexpat': {
47+
'includes': ['lib'],
48+
'lib_links': ['-lexpat'],
49+
'lib_path': join('expat', 'lib', '.libs'),
50+
},
51+
}
52+
53+
1254
class Python3Recipe(TargetPythonRecipe):
1355
version = 'bpo-30386'
1456
url = 'https://github.com/inclement/cpython/archive/{version}.zip'
1557
name = 'python3'
1658

1759
depends = ['hostpython3']
1860
conflicts = ['python3crystax', 'python2']
19-
# opt_depends = ['openssl', 'sqlite3']
61+
opt_depends = ['libffi', 'libexpat', 'openssl', 'sqlite3']
62+
# TODO: More patches maybe be needed, but with those
63+
# two we successfully build and run a simple app
64+
patches = ['patches/python-3.x.x-libs.patch',
65+
'patches/fix-termios.patch']
66+
67+
def set_libs_flags(self, env, arch=None):
68+
# Takes an env as argument and adds cflags/ldflags
69+
# based on libs_info and versioned_libs.
70+
env['OPENSSL_BUILD'] = '/path-to-openssl'
71+
env['SQLITE3_INC_DIR'] = '/path-to-sqlite3-includes'
72+
env['SQLITE3_LIB_DIR'] = '/path-to-sqlite3-library'
73+
74+
for lib in self.opt_depends:
75+
if lib in self.ctx.recipe_build_order:
76+
logger.info(
77+
''.join((Err_Fore.MAGENTA, '-> Activating flags for ', lib,
78+
Err_Fore.RESET)))
79+
r = Recipe.get_recipe(lib, self.ctx)
80+
b = r.get_build_dir(arch.arch)
81+
82+
# Sets or modifies include/library base paths,
83+
# this should point to build directory, and some
84+
# libs has special build directories...so...
85+
# here we deal with it.
86+
inc_dir = b
87+
lib_dir = b
88+
if lib == 'sqlite3':
89+
lib_dir = r.get_lib_dir(arch)
90+
elif lib == 'libffi':
91+
inc_dir = join(inc_dir, r.get_host(arch))
92+
lib_dir = join(lib_dir, r.get_host(arch))
93+
elif lib == 'libexpat':
94+
inc_dir = join(b, 'expat')
95+
96+
# It establishes the include's flags taking into
97+
# account the information provided in libs_info.
98+
if libs_info[lib]['includes']:
99+
includes = [
100+
join(inc_dir, p) for p in libs_info[lib]['includes']]
101+
else:
102+
includes = [inc_dir]
103+
i_flags = ' -I' + ' -I'.join(includes)
104+
105+
# It establishes the linking's flags taking into
106+
# account the information provided in libs_info.
107+
if libs_info[lib]['lib_path']:
108+
lib_dir = join(lib_dir, libs_info[lib]['lib_path'])
109+
if lib not in versioned_libs:
110+
l_flags = ' -L' + lib_dir + ' ' + ' '.join(
111+
libs_info[lib]['lib_links'])
112+
else:
113+
l_flags = ' -L' + lib_dir + ' ' + ' '.join(
114+
[i + r.version for i in libs_info[lib]['lib_links']])
115+
116+
# Inserts or appends to env.
117+
f = 'CPPFLAGS'
118+
env[f] = env[f] + i_flags if f in env else i_flags
119+
f = 'LDFLAGS'
120+
env[f] = env[f] + l_flags if f in env else l_flags
121+
122+
# Sets special python compilation flags for some libs.
123+
# The openssl and sqlite env variables are set
124+
# via patch: patches/python-3.x.x-libs.patch
125+
if lib == 'openssl':
126+
env['OPENSSL_BUILD'] = b
127+
env['OPENSSL_VERSION'] = r.version
128+
elif lib == 'sqlite3':
129+
env['SQLITE3_INC_DIR'] = inc_dir
130+
env['SQLITE3_LIB_DIR'] = lib_dir
131+
elif lib == 'libffi':
132+
env['LIBFFI_CFLAGS'] = env['CFLAGS'] + i_flags
133+
env['LIBFFI_LIBS'] = l_flags
134+
return env
20135

21136
def build_arch(self, arch):
22137
recipe_build_dir = self.get_build_dir(arch.arch)
@@ -80,22 +195,36 @@ def build_arch(self, arch):
80195

81196
env['SYSROOT'] = sysroot
82197

198+
# TODO: All the env variables should be moved
199+
# into method: get_recipe_env (all above included)
200+
env = self.set_libs_flags(env, arch)
201+
202+
# Arguments for python configure
203+
# TODO: move ac_xx_ arguments to config.site
204+
configure_args = [
205+
'--host={android_host}',
206+
'--build={android_build}',
207+
'--enable-shared',
208+
'--disable-ipv6',
209+
'--without-ensurepip',
210+
'--prefix={prefix}',
211+
'--exec-prefix={exec_prefix}',
212+
'ac_cv_file__dev_ptmx=yes',
213+
'ac_cv_file__dev_ptc=no',
214+
'ac_cv_little_endian_double=yes'
215+
]
216+
if 'libffi' in self.ctx.recipe_build_order:
217+
configure_args.append('--with-system-ffi')
218+
if 'libexpat' in self.ctx.recipe_build_order:
219+
configure_args.append('--with-system-expat')
220+
83221
if not exists('config.status'):
84222
shprint(sh.Command(join(recipe_build_dir, 'configure')),
85-
*(' '.join(('--host={android_host}',
86-
'--build={android_build}',
87-
'--enable-shared',
88-
'--disable-ipv6',
89-
'ac_cv_file__dev_ptmx=yes',
90-
'ac_cv_file__dev_ptc=no',
91-
'--without-ensurepip',
92-
'ac_cv_little_endian_double=yes',
93-
'--prefix={prefix}',
94-
'--exec-prefix={exec_prefix}')).format(
95-
android_host=android_host,
96-
android_build=android_build,
97-
prefix=sys_prefix,
98-
exec_prefix=sys_exec_prefix)).split(' '), _env=env)
223+
*(' '.join(configure_args).format(
224+
android_host=android_host,
225+
android_build=android_build,
226+
prefix=sys_prefix,
227+
exec_prefix=sys_exec_prefix)).split(' '), _env=env)
99228

100229
if not exists('python'):
101230
shprint(sh.make, 'all', _env=env)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--- Python-2.7.2.orig/Modules/termios.c 2012-06-12 02:49:39.780162534 +0200
2+
+++ Python-2.7.2/Modules/termios.c 2012-06-12 02:51:52.092157828 +0200
3+
@@ -227,6 +227,7 @@
4+
return Py_None;
5+
}
6+
7+
+#if 0 // No tcdrain defined for Android.
8+
PyDoc_STRVAR(termios_tcdrain__doc__,
9+
"tcdrain(fd) -> None\n\
10+
\n\
11+
@@ -246,6 +247,7 @@
12+
Py_INCREF(Py_None);
13+
return Py_None;
14+
}
15+
+#endif
16+
17+
PyDoc_STRVAR(termios_tcflush__doc__,
18+
"tcflush(fd, queue) -> None\n\
19+
@@ -301,8 +303,10 @@
20+
METH_VARARGS, termios_tcsetattr__doc__},
21+
{"tcsendbreak", termios_tcsendbreak,
22+
METH_VARARGS, termios_tcsendbreak__doc__},
23+
+#if 0 // No tcdrain defined for Android.
24+
{"tcdrain", termios_tcdrain,
25+
METH_VARARGS, termios_tcdrain__doc__},
26+
+#endif
27+
{"tcflush", termios_tcflush,
28+
METH_VARARGS, termios_tcflush__doc__},
29+
{"tcflow", termios_tcflow,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
diff -Naurp cpython-bpo-30386.orig/setup.py cpython-bpo-30386/setup.py
2+
--- cpython-bpo-30386.orig/setup.py 2018-09-15 23:45:17.000000000 +0200
3+
+++ cpython-bpo-30386/setup.py 2018-10-18 13:12:59.798278300 +0200
4+
@@ -859,6 +870,7 @@ class PyBuildExt(build_ext):
5+
depends = ['socketmodule.h']) )
6+
# Detect SSL support for the socket module (via _ssl)
7+
search_for_ssl_incs_in = [
8+
+ os.path.join(os.environ["OPENSSL_BUILD"], 'include'),
9+
'/usr/local/ssl/include',
10+
'/usr/contrib/ssl/include/'
11+
]
12+
@@ -871,7 +883,8 @@ class PyBuildExt(build_ext):
13+
if krb5_h:
14+
ssl_incs += krb5_h
15+
ssl_libs = find_library_file(self.compiler, 'ssl',lib_dirs,
16+
- ['/usr/local/ssl/lib',
17+
+ [os.environ["OPENSSL_BUILD"],
18+
+ '/usr/local/ssl/lib',
19+
'/usr/contrib/ssl/lib/'
20+
] )
21+
22+
@@ -1177,7 +1190,13 @@ class PyBuildExt(build_ext):
23+
'/usr/local/include/sqlite3',
24+
]
25+
if cross_compiling:
26+
- sqlite_inc_paths = []
27+
+ # The common install prefix of 3rd party headers used during
28+
+ # cross compilation
29+
+ mydir = os.environ.get('PYTHON_XCOMPILE_DEPENDENCIES_PREFIX')
30+
+ if mydir:
31+
+ sqlite_inc_paths = [mydir + '/include']
32+
+ else:
33+
+ sqlite_inc_paths = []
34+
MIN_SQLITE_VERSION_NUMBER = (3, 0, 8)
35+
MIN_SQLITE_VERSION = ".".join([str(x)
36+
for x in MIN_SQLITE_VERSION_NUMBER])
37+
@@ -1229,7 +1248,9 @@ class PyBuildExt(build_ext):
38+
if sqlite_libfile:
39+
sqlite_libdir = [os.path.abspath(os.path.dirname(sqlite_libfile))]
40+
41+
- if sqlite_incdir and sqlite_libdir:
42+
+ sqlite_incdir = os.environ["SQLITE3_INC_DIR"]
43+
+ sqlite_libdir = [os.environ["SQLITE3_LIB_DIR"]]
44+
+ if os.path.isdir(sqlite_incdir) and os.path.isdir(sqlite_libdir[0]):
45+
sqlite_srcs = ['_sqlite/cache.c',
46+
'_sqlite/connection.c',
47+
'_sqlite/cursor.c',

0 commit comments

Comments
 (0)