Skip to content

Commit 7f4652a

Browse files
authored
recipes: add scipy support (#2370)
* add scipy support * flake8 * remove ccache check * remove lapack, scipy from ci due to missing gfortran in CI machines * better recipe names, imports
1 parent c37c304 commit 7f4652a

9 files changed

+198
-47
lines changed

ci/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class TargetPython(Enum):
2626
# mpmath package with a version >= 0.19 required
2727
'sympy',
2828
'vlc',
29+
# need extra gfortran NDK system add-on
30+
'lapack', 'scipy',
2931
])
3032

3133
BROKEN_RECIPES = {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from pythonforandroid.recipe import Recipe
2+
from pythonforandroid.logger import shprint
3+
from pythonforandroid.util import current_directory, ensure_dir, BuildInterruptingException
4+
from multiprocessing import cpu_count
5+
from os.path import join
6+
import sh
7+
8+
9+
class LapackRecipe(Recipe):
10+
11+
name = 'lapack'
12+
version = 'v3.9.0'
13+
url = 'https://github.com/Reference-LAPACK/lapack/archive/{version}.tar.gz'
14+
libdir = 'build/install/lib'
15+
built_libraries = {'libblas.so': libdir, 'liblapack.so': libdir, 'libcblas.so': libdir}
16+
need_stl_shared = True
17+
18+
def get_recipe_env(self, arch):
19+
env = super().get_recipe_env(arch)
20+
sysroot = f"{self.ctx.ndk_dir}/platforms/{env['NDK_API']}/{arch.platform_dir}"
21+
FC = f"{env['TOOLCHAIN_PREFIX']}-gfortran"
22+
env['FC'] = f'{FC} --sysroot={sysroot}'
23+
if sh.which(FC) is None:
24+
raise BuildInterruptingException(f"{FC} not found. See https://github.com/mzakharo/android-gfortran")
25+
return env
26+
27+
def build_arch(self, arch):
28+
source_dir = self.get_build_dir(arch.arch)
29+
build_target = join(source_dir, 'build')
30+
install_target = join(build_target, 'install')
31+
32+
ensure_dir(build_target)
33+
with current_directory(build_target):
34+
env = self.get_recipe_env(arch)
35+
shprint(sh.rm, '-rf', 'CMakeFiles/', 'CMakeCache.txt', _env=env)
36+
shprint(sh.cmake, source_dir,
37+
'-DCMAKE_SYSTEM_NAME=Android',
38+
'-DCMAKE_POSITION_INDEPENDENT_CODE=1',
39+
'-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch),
40+
'-DCMAKE_ANDROID_NDK=' + self.ctx.ndk_dir,
41+
'-DCMAKE_BUILD_TYPE=Release',
42+
'-DCMAKE_INSTALL_PREFIX={}'.format(install_target),
43+
'-DANDROID_ABI={arch}'.format(arch=arch.arch),
44+
'-DANDROID_ARM_NEON=ON',
45+
'-DENABLE_NEON=ON',
46+
'-DCBLAS=ON',
47+
'-DBUILD_SHARED_LIBS=ON',
48+
_env=env)
49+
shprint(sh.make, '-j' + str(cpu_count()), _env=env)
50+
shprint(sh.make, 'install', _env=env)
51+
52+
53+
recipe = LapackRecipe()

pythonforandroid/recipes/numpy/__init__.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
2+
from pythonforandroid.logger import shprint, info
3+
from pythonforandroid.util import current_directory
24
from multiprocessing import cpu_count
35
from os.path import join
6+
import glob
7+
import sh
48

59

610
class NumpyRecipe(CompiledComponentsPythonRecipe):
@@ -9,23 +13,47 @@ class NumpyRecipe(CompiledComponentsPythonRecipe):
913
url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip'
1014
site_packages_name = 'numpy'
1115
depends = ['setuptools', 'cython']
16+
install_in_hostpython = True
17+
call_hostpython_via_targetpython = False
1218

1319
patches = [
20+
join('patches', 'hostnumpy-xlocale.patch'),
21+
join('patches', 'remove-default-paths.patch'),
1422
join('patches', 'add_libm_explicitly_to_build.patch'),
15-
join('patches', 'do_not_use_system_libs.patch'),
16-
join('patches', 'remove_unittest_call.patch'),
23+
join('patches', 'compiler_cxx_fix.patch'),
1724
]
1825

19-
call_hostpython_via_targetpython = False
26+
def _build_compiled_components(self, arch):
27+
info('Building compiled components in {}'.format(self.name))
28+
29+
env = self.get_recipe_env(arch)
30+
with current_directory(self.get_build_dir(arch.arch)):
31+
hostpython = sh.Command(self.real_hostpython_location)
32+
if self.install_in_hostpython:
33+
shprint(hostpython, 'setup.py', 'clean', '--all', '--force', _env=env)
34+
hostpython = sh.Command(self.hostpython_location)
35+
shprint(hostpython, 'setup.py', self.build_cmd, '-v',
36+
_env=env, *self.setup_extra_args)
37+
build_dir = glob.glob('build/lib.*')[0]
38+
shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
39+
env['STRIP'], '{}', ';', _env=env)
40+
41+
def _rebuild_compiled_components(self, arch, env):
42+
info('Rebuilding compiled components in {}'.format(self.name))
43+
44+
hostpython = sh.Command(self.real_hostpython_location)
45+
shprint(hostpython, 'setup.py', 'clean', '--all', '--force', _env=env)
46+
shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
47+
*self.setup_extra_args)
2048

2149
def build_compiled_components(self, arch):
2250
self.setup_extra_args = ['-j', str(cpu_count())]
23-
super().build_compiled_components(arch)
51+
self._build_compiled_components(arch)
2452
self.setup_extra_args = []
2553

2654
def rebuild_compiled_components(self, arch, env):
2755
self.setup_extra_args = ['-j', str(cpu_count())]
28-
super().rebuild_compiled_components(arch, env)
56+
self._rebuild_compiled_components(arch, env)
2957
self.setup_extra_args = []
3058

3159

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
diff --git a/numpy/distutils/ccompiler.py b/numpy/distutils/ccompiler.py
2+
index 6438790..a5f1527 100644
3+
--- a/numpy/distutils/ccompiler.py
4+
+++ b/numpy/distutils/ccompiler.py
5+
@@ -686,13 +686,13 @@ def CCompiler_cxx_compiler(self):
6+
return self
7+
8+
cxx = copy(self)
9+
- cxx.compiler_so = [cxx.compiler_cxx[0]] + cxx.compiler_so[1:]
10+
+ cxx.compiler_so = cxx.compiler_cxx + cxx.compiler_so[1:]
11+
if sys.platform.startswith('aix') and 'ld_so_aix' in cxx.linker_so[0]:
12+
# AIX needs the ld_so_aix script included with Python
13+
cxx.linker_so = [cxx.linker_so[0], cxx.compiler_cxx[0]] \
14+
+ cxx.linker_so[2:]
15+
else:
16+
- cxx.linker_so = [cxx.compiler_cxx[0]] + cxx.linker_so[1:]
17+
+ cxx.linker_so = cxx.compiler_cxx + cxx.linker_so[1:]
18+
return cxx
19+
20+
replace_method(CCompiler, 'cxx_compiler', CCompiler_cxx_compiler)

pythonforandroid/recipes/numpy/patches/do_not_use_system_libs.patch

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
diff --git a/numpy/core/src/common/numpyos.c b/numpy/core/src/common/numpyos.c
2+
index d60b1ca..8972144 100644
3+
--- a/numpy/core/src/common/numpyos.c
4+
+++ b/numpy/core/src/common/numpyos.c
5+
@@ -20,7 +20,7 @@
6+
* the defines from xlocale.h are included in locale.h on some systems;
7+
* see gh-8367
8+
*/
9+
- #include <xlocale.h>
10+
+ #include <locale.h>
11+
#endif
12+
#endif
13+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
2+
index fc7018a..7b514bc 100644
3+
--- a/numpy/distutils/system_info.py
4+
+++ b/numpy/distutils/system_info.py
5+
@@ -340,10 +340,10 @@ if os.path.join(sys.prefix, 'lib') not in default_lib_dirs:
6+
default_include_dirs.append(os.path.join(sys.prefix, 'include'))
7+
default_src_dirs.append(os.path.join(sys.prefix, 'src'))
8+
9+
-default_lib_dirs = [_m for _m in default_lib_dirs if os.path.isdir(_m)]
10+
-default_runtime_dirs = [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
11+
-default_include_dirs = [_m for _m in default_include_dirs if os.path.isdir(_m)]
12+
-default_src_dirs = [_m for _m in default_src_dirs if os.path.isdir(_m)]
13+
+default_lib_dirs = [] #[_m for _m in default_lib_dirs if os.path.isdir(_m)]
14+
+default_runtime_dirs =[] # [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
15+
+default_include_dirs =[] # [_m for _m in default_include_dirs if os.path.isdir(_m)]
16+
+default_src_dirs =[] # [_m for _m in default_src_dirs if os.path.isdir(_m)]
17+
18+
so_ext = get_shared_lib_extension()
19+
20+
@@ -814,7 +814,7 @@ class system_info(object):
21+
path = self.get_paths(self.section, key)
22+
if path == ['']:
23+
path = []
24+
- return path
25+
+ return []
26+
27+
def get_include_dirs(self, key='include_dirs'):
28+
return self.get_paths(self.section, key)

pythonforandroid/recipes/numpy/patches/remove_unittest_call.patch

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
2+
from multiprocessing import cpu_count
3+
from os.path import join
4+
5+
6+
class ScipyRecipe(CompiledComponentsPythonRecipe):
7+
8+
version = '1.5.4'
9+
url = f'https://github.com/scipy/scipy/releases/download/v{version}/scipy-{version}.zip'
10+
site_packages_name = 'scipy'
11+
depends = ['setuptools', 'cython', 'numpy', 'lapack']
12+
call_hostpython_via_targetpython = False
13+
14+
def build_compiled_components(self, arch):
15+
self.setup_extra_args = ['-j', str(cpu_count())]
16+
super().build_compiled_components(arch)
17+
self.setup_extra_args = []
18+
19+
def rebuild_compiled_components(self, arch, env):
20+
self.setup_extra_args = ['-j', str(cpu_count())]
21+
super().rebuild_compiled_components(arch, env)
22+
self.setup_extra_args = []
23+
24+
def get_recipe_env(self, arch):
25+
env = super().get_recipe_env(arch)
26+
27+
GCC_VER = '4.9'
28+
HOST = 'linux-x86_64'
29+
LIB = 'lib64'
30+
31+
prefix = env['TOOLCHAIN_PREFIX']
32+
lapack_dir = join(Recipe.get_recipe('lapack', self.ctx).get_build_dir(arch.arch), 'build', 'install')
33+
sysroot = f"{self.ctx.ndk_dir}/platforms/{env['NDK_API']}/{arch.platform_dir}"
34+
sysroot_include = f'{self.ctx.ndk_dir}/toolchains/llvm/prebuilt/{HOST}/sysroot/usr/include'
35+
libgfortran = f'{self.ctx.ndk_dir}/toolchains/{prefix}-{GCC_VER}/prebuilt/{HOST}/{prefix}/{LIB}'
36+
numpylib = self.ctx.get_python_install_dir() + '/numpy/core/lib'
37+
LDSHARED_opts = env['LDSHARED'].split('clang')[1]
38+
39+
env['LAPACK'] = f'{lapack_dir}/lib'
40+
env['BLAS'] = env['LAPACK']
41+
env['F90'] = f'{prefix}-gfortran'
42+
env['CXX'] += f' -Wl,-l{self.stl_lib_name} -Wl,-L{self.get_stl_lib_dir(arch)}'
43+
env['CPPFLAGS'] += f' --sysroot={sysroot} -I{sysroot_include}/c++/v1 -I{sysroot_include}'
44+
env['LDSHARED'] = 'clang'
45+
env['LDFLAGS'] += f' {LDSHARED_opts} --sysroot={sysroot} -L{libgfortran} -L{numpylib}'
46+
return env
47+
48+
49+
recipe = ScipyRecipe()

0 commit comments

Comments
 (0)