Skip to content

Commit 4f7aee6

Browse files
author
agilewalker
committed
Fix building ssl for python3
1 parent 73a6ae8 commit 4f7aee6

File tree

2 files changed

+206
-51
lines changed

2 files changed

+206
-51
lines changed

pythonforandroid/recipes/openssl/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def build_arch(self, arch):
5959
if all(map(check_crypto, ('SSLeay', 'MD5_Transform', 'MD4_Init'))):
6060
break
6161
shprint(sh.make, 'clean', _env=env)
62-
63-
self.install_libs(arch, 'libssl' + self.version + '.so',
64-
'libcrypto' + self.version + '.so')
62+
63+
self.install_libs(arch, 'libssl.a', 'libssl' + self.version + '.so',
64+
'libcrypto.a', 'libcrypto' + self.version + '.so')
6565

6666
recipe = OpenSSLRecipe()
Lines changed: 203 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,231 @@
1-
21
from pythonforandroid.recipe import TargetPythonRecipe
32
from pythonforandroid.toolchain import shprint, current_directory, ArchARM
43
from pythonforandroid.logger import info, error
54
from pythonforandroid.util import ensure_dir, temp_directory
65
from os.path import exists, join
6+
import os
77
import glob
88
import sh
9-
10-
prebuilt_download_locations = {
11-
'3.6': ('https://github.com/inclement/crystax_python_builds/'
12-
'releases/download/0.1/crystax_python_3.6_armeabi_armeabi-v7a.tar.gz')}
9+
from sh import Command
10+
11+
# This is the content of opensslconf.h taken from
12+
# ndkdir/build/tools/build-target-openssl.sh
13+
OPENSSLCONF = """#if defined(__ARM_ARCH_5TE__)
14+
#include "opensslconf_armeabi.h"
15+
#elif defined(__ARM_ARCH_7A__) && !defined(__ARM_PCS_VFP)
16+
#include "opensslconf_armeabi_v7a.h"
17+
#elif defined(__ARM_ARCH_7A__) && defined(__ARM_PCS_VFP)
18+
#include "opensslconf_armeabi_v7a_hard.h"
19+
#elif defined(__aarch64__)
20+
#include "opensslconf_arm64_v8a.h"
21+
#elif defined(__i386__)
22+
#include "opensslconf_x86.h"
23+
#elif defined(__x86_64__)
24+
#include "opensslconf_x86_64.h"
25+
#elif defined(__mips__) && !defined(__mips64)
26+
#include "opensslconf_mips.h"
27+
#elif defined(__mips__) && defined(__mips64)
28+
#include "opensslconf_mips64.h"
29+
#else
30+
#error "Unsupported ABI"
31+
#endif
32+
"""
33+
34+
35+
def realpath(fname):
36+
"""
37+
Own implementation of os.realpath which may be broken in some python versions
38+
Returns: the absolute path o
39+
40+
"""
41+
42+
if not os.path.islink(fname):
43+
return os.path.abspath(fname)
44+
45+
abs_path = os.path.abspath(fname).split(os.sep)[:-1]
46+
rel_path = os.readlink(fname)
47+
48+
if os.path.abspath(rel_path) == rel_path:
49+
return rel_path
50+
51+
rel_path = rel_path.split(os.sep)
52+
for folder in rel_path:
53+
if folder == '..':
54+
abs_path.pop()
55+
else:
56+
abs_path.append(folder)
57+
return os.sep.join(abs_path)
1358

1459
class Python3Recipe(TargetPythonRecipe):
1560
version = '3.5'
16-
url = ''
61+
url = ('https://github.com/crystax/android-vendor-python-{}-{}'
62+
'/archive/master.tar.gz'.format(*version.split('.')))
1763
name = 'python3crystax'
1864

1965
depends = ['hostpython3crystax']
2066
conflicts = ['python2', 'python3']
2167

2268
from_crystax = True
2369

70+
def download_if_necessary(self):
71+
if 'openssl' in self.ctx.recipe_build_order:
72+
super(Python3Recipe, self).download_if_necessary()
73+
2474
def get_dir_name(self):
2575
name = super(Python3Recipe, self).get_dir_name()
2676
name += '-version{}'.format(self.version)
2777
return name
2878

29-
def build_arch(self, arch):
30-
# We don't have to actually build anything as CrystaX comes
31-
# with the necessary modules. They are included by modifying
32-
# the Android.mk in the jni folder.
33-
34-
# If the Python version to be used is not prebuilt with the CrystaX
35-
# NDK, we do have to download it.
36-
37-
crystax_python_dir = join(self.ctx.ndk_dir, 'sources', 'python')
38-
if not exists(join(crystax_python_dir, self.version)):
39-
info(('The NDK does not have a prebuilt Python {}, trying '
40-
'to obtain one.').format(self.version))
41-
42-
if self.version not in prebuilt_download_locations:
43-
error(('No prebuilt version for Python {} could be found, '
44-
'the built cannot continue.'))
45-
exit(1)
46-
47-
with temp_directory() as td:
48-
self.download_file(prebuilt_download_locations[self.version],
49-
join(td, 'downloaded_python'))
50-
shprint(sh.tar, 'xf', join(td, 'downloaded_python'),
51-
'--directory', crystax_python_dir)
52-
53-
if not exists(join(crystax_python_dir, self.version)):
54-
error(('Something went wrong, the directory at {} should '
55-
'have been created but does not exist.').format(
56-
join(crystax_python_dir, self.version)))
57-
58-
if not exists(join(
59-
crystax_python_dir, self.version, 'libs', arch.arch)):
60-
error(('The prebuilt Python for version {} does not contain '
61-
'binaries for your chosen architecture "{}".').format(
62-
self.version, arch.arch))
63-
exit(1)
64-
65-
# TODO: We should have an option to build a new Python. This
66-
# would also allow linking to openssl and sqlite from CrystaX.
79+
def copy_include_dir(self, source, target):
80+
ensure_dir(target)
81+
for fname in os.listdir(source):
82+
sh.ln('-sf', realpath(join(source, fname)), join(target, fname))
83+
84+
def _patch_dev_defaults(self, fp, target_ver):
85+
for line in fp:
86+
if 'OPENSSL_VERSIONS=' in line:
87+
versions = line.split('"')[1].split(' ')
88+
if versions[0] == target_ver:
89+
raise ValueError('Patch not needed')
90+
91+
if target_ver in versions:
92+
versions.remove(target_ver)
93+
94+
versions.insert(0, target_ver)
95+
96+
yield 'OPENSSL_VERSIONS="{}"\n'.format(' '.join(versions))
97+
else:
98+
yield line
99+
100+
def patch_dev_defaults(self, ssl_recipe):
101+
def_fname = join(self.ctx.ndk_dir, 'build', 'tools', 'dev-defaults.sh')
102+
try:
103+
with open(def_fname, 'r') as fp:
104+
s = ''.join(self._patch_dev_defaults(fp,
105+
str(ssl_recipe.version)))
106+
with open(def_fname, 'w') as fp:
107+
fp.write(s)
108+
109+
except ValueError:
110+
pass
111+
112+
def check_for_sslso(self, ssl_recipe, arch):
113+
# type: (Recipe, str)
114+
dynlib_dir = join(self.ctx.ndk_dir, 'sources', 'python', self.version,
115+
'libs', arch.arch, 'modules')
116+
117+
if os.path.exists(join(dynlib_dir, '_ssl.so')):
118+
return 10, 'Shared object exists in ndk'
67119

120+
# find out why _ssl.so is missing
121+
122+
source_dir = join(self.ctx.ndk_dir, 'sources', 'openssl', ssl_recipe.version)
123+
if not os.path.exists(source_dir):
124+
return 0, 'Openssl version not present'
125+
126+
# these two path checks are lifted straight from:
127+
# crystax-ndk/build/tools/build-target-python.sh
128+
if not os.path.exists(join(source_dir, 'Android.mk')):
129+
return 1.1, 'Android.mk is missing in openssl source'
130+
131+
include_dir = join(source_dir, 'include','openssl')
132+
if not os.path.exists(join(include_dir, 'opensslconf.h')):
133+
return 1.2, 'Openssl include dir missing'
134+
135+
under_scored_arch = arch.arch.replace('-', '_')
136+
if not os.path.lexists(join(include_dir,
137+
'opensslconf_{}.h'.format(under_scored_arch))):
138+
return 1.3, 'Opensslconf arch header missing from include'
139+
140+
141+
142+
# lastly a check to see whether shared objects for the correct arch
143+
# is present in the ndk
144+
if not os.path.exists(join(source_dir, 'libs', arch.arch)):
145+
return 2, 'Openssl libs for this arch is missing in ndk'
146+
147+
return 5, 'Ready to recompile python'
148+
149+
def find_Android_mk(self):
150+
openssl_dir = join(self.ctx.ndk_dir, 'sources', 'openssl')
151+
for version in os.listdir(openssl_dir):
152+
mk_path = join(openssl_dir, version, 'Android.mk')
153+
if os.path.exists(mk_path):
154+
return mk_path
155+
156+
def build_arch(self, arch):
157+
# If openssl is needed we may have to recompile cPython to get the
158+
# ssl.py module working properly
159+
if self.from_crystax and 'openssl' in self.ctx.recipe_build_order:
160+
info('Openssl and crystax-python combination may require '
161+
'recompilation of python...')
162+
ssl_recipe = self.get_recipe('openssl', self.ctx)
163+
stage, msg = self.check_for_sslso(ssl_recipe, arch)
164+
stage = 0 if stage < 5 else stage
165+
info(msg)
166+
openssl_build_dir = ssl_recipe.get_build_dir(arch.arch)
167+
openssl_ndk_dir = join(self.ctx.ndk_dir, 'sources', 'openssl',
168+
ssl_recipe.version)
169+
170+
if stage < 2:
171+
info('Copying openssl headers and Android.mk to ndk')
172+
ensure_dir(openssl_ndk_dir)
173+
if stage < 1.2:
174+
# copy include folder and Android.mk to ndk
175+
mk_path = self.find_Android_mk()
176+
if mk_path is None:
177+
raise IOError('Android.mk file could not be found in '
178+
'any versions in ndk->sources->openssl')
179+
shprint(sh.cp, mk_path, openssl_ndk_dir)
180+
181+
include_dir = join(openssl_build_dir, 'include')
182+
if stage < 1.3:
183+
ndk_include_dir = join(openssl_ndk_dir, 'include', 'openssl')
184+
self.copy_include_dir(join(include_dir, 'openssl'), ndk_include_dir)
185+
186+
target_conf = join(openssl_ndk_dir, 'include', 'openssl',
187+
'opensslconf.h')
188+
shprint(sh.rm, '-f', target_conf)
189+
# overwrite opensslconf.h
190+
with open(target_conf, 'w') as fp:
191+
fp.write(OPENSSLCONF)
192+
193+
if stage < 1.4:
194+
# move current conf to arch specific conf in ndk
195+
under_scored_arch = arch.arch.replace('-', '_')
196+
shprint(sh.ln, '-sf',
197+
realpath(join(include_dir, 'openssl', 'opensslconf.h')),
198+
join(openssl_ndk_dir, 'include', 'openssl',
199+
'opensslconf_{}.h'.format(under_scored_arch))
200+
)
201+
202+
if stage < 3:
203+
info('Copying openssl libs to ndk')
204+
arch_ndk_lib = join(openssl_ndk_dir, 'libs', arch.arch)
205+
ensure_dir(arch_ndk_lib)
206+
info(openssl_ndk_dir)
207+
info(arch_ndk_lib)
208+
shprint(sh.mv, join(openssl_build_dir, 'libcrypto{}.so'.format(ssl_recipe.version)), join(openssl_build_dir, 'libcrypto.so'))
209+
shprint(sh.mv, join(openssl_build_dir, 'libssl{}.so'.format(ssl_recipe.version)), join(openssl_build_dir, 'libssl.so'))
210+
libs = ['libcrypto.a', 'libcrypto.so', 'libssl.a', 'libssl.so']
211+
cmd = [join(openssl_build_dir, lib) for lib in libs] + [arch_ndk_lib]
212+
shprint(sh.cp, '-f', *cmd)
213+
214+
if stage < 10:
215+
info('Recompiling python-crystax')
216+
self.patch_dev_defaults(ssl_recipe)
217+
build_script = join(self.ctx.ndk_dir, 'build', 'tools',
218+
'build-target-python.sh')
219+
220+
shprint(Command(build_script),
221+
'--ndk-dir={}'.format(self.ctx.ndk_dir),
222+
'--abis={}'.format(arch.arch),
223+
'-j5', '--verbose',
224+
self.get_build_dir(arch.arch))
225+
226+
info('Extracting CrystaX python3 from NDK package')
68227
dirn = self.ctx.get_python_install_dir()
69228
ensure_dir(dirn)
70-
71-
# Instead of using a locally built hostpython, we use the
72-
# user's Python for now. They must have the right version
73-
# available. Using e.g. pyenv makes this easy.
74229
self.ctx.hostpython = 'python{}'.format(self.version)
75230

76231
recipe = Python3Recipe()

0 commit comments

Comments
 (0)