-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix building ssl for python3 #1217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,257 @@ | ||
|
||
from pythonforandroid.recipe import TargetPythonRecipe | ||
from pythonforandroid.toolchain import shprint, current_directory, ArchARM | ||
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.4' | ||
} | ||
|
||
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) | ||
|
||
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')} | ||
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.5' | ||
url = '' | ||
name = 'python3crystax' | ||
|
||
depends = ['hostpython3crystax'] | ||
depends = ['hostpython3crystax'] | ||
conflicts = ['python2', 'python3'] | ||
|
||
from_crystax = True | ||
|
||
def download_if_necessary(self): | ||
if 'openssl' in self.ctx.recipe_build_order or self.version == '3.6': | ||
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 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 prebuild_arch(self, arch): | ||
super(Python3Recipe, self).prebuild_arch(arch) | ||
if self.version == '3.6': | ||
Python3Recipe.patches = ['patch_python3.6.patch'] | ||
build_dir = self.get_build_dir(arch.arch) | ||
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(ndk_build_tools_python_dir, python_build_file+'.3.5'), | ||
join(ndk_build_tools_python_dir, python_build_file+'.3.6')) | ||
ndk_sources_python_dir = join(self.ctx.ndk_dir, 'sources', 'python') | ||
if not os.path.exists(join(ndk_sources_python_dir, '3.6')): | ||
os.mkdir(join(ndk_sources_python_dir, '3.6')) | ||
sh.sed('s#3.5#3.6#', | ||
join(ndk_sources_python_dir, '3.5/Android.mk'), | ||
_out=join(ndk_sources_python_dir, '3.6/Android.mk')) | ||
|
||
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. | ||
# 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: | ||
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) | ||
|
||
# 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) | ||
|
||
recipe = Python3Recipe() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.