Skip to content

Commit a5fc2ad

Browse files
committed
scipy: update to v1.15.2
1 parent f491c6e commit a5fc2ad

File tree

15 files changed

+454
-1308
lines changed

15 files changed

+454
-1308
lines changed

Makefile

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,6 @@ testapps-with-numpy/%: virtualenv
6060
--arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 \
6161
--permission "(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)" --permission "(name=android.permission.INTERNET)"
6262

63-
testapps-with-scipy: testapps-with-scipy/debug/apk testapps-with-scipy/release/aab
64-
65-
# testapps-with-scipy/MODE/ARTIFACT
66-
testapps-with-scipy/%: virtualenv
67-
$(eval MODE := $(word 2, $(subst /, ,$@)))
68-
$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
69-
@echo Building testapps-with-scipy for $(MODE) mode and $(ARTIFACT) artifact
70-
. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
71-
export LEGACY_NDK=$(ANDROID_NDK_HOME_LEGACY) && \
72-
python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
73-
--requirements python3,scipy,kivy \
74-
--arch=armeabi-v7a --arch=arm64-v8a
75-
7663
testapps-webview: testapps-webview/debug/apk testapps-webview/release/aab
7764

7865
# testapps-webview/MODE/ARTIFACT

ci/constants.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ class TargetPython(Enum):
2525
# mpmath package with a version >= 0.19 required
2626
'sympy',
2727
'vlc',
28-
# need extra gfortran NDK system add-on
29-
'lapack', 'scipy',
3028
# Outdated and there's a chance that is now useless.
3129
'zope_interface',
3230
# Requires zope_interface, which is broken.

pythonforandroid/recipe.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class Recipe(metaclass=RecipeMeta):
7676

7777
_version = None
7878
'''A string giving the version of the software the recipe describes,
79-
e.g. ``2.0.3`` or ``master``.'''
79+
e.g. ``2.0.3``. In case of git repository version is branch or a tagged release e.g. ``v12.3`` or ``develop``.'''
8080

8181
md5sum = None
8282
'''The md5sum of the source from the :attr:`url`. Non-essential, but
@@ -109,7 +109,7 @@ class Recipe(metaclass=RecipeMeta):
109109
recipe if they are built at all, but whose presence is not essential.'''
110110

111111
patches = []
112-
'''A list of patches to apply to the source. Values can be either a string
112+
'''Alist of patches to apply to the source. Values can be either a string
113113
referring to the patch file relative to the recipe dir, or a tuple of the
114114
string patch file and a callable, which will receive the kwargs `arch` and
115115
`recipe`, which should return True if the patch should be applied.'''
@@ -155,6 +155,11 @@ class Recipe(metaclass=RecipeMeta):
155155
starting from NDK r18 the `gnustl_shared` lib has been deprecated.
156156
'''
157157

158+
min_ndk_api_support = 20
159+
'''
160+
Minimum ndk api your recipe will support
161+
'''
162+
158163
def get_stl_library(self, arch):
159164
return join(
160165
arch.ndk_lib_dir,
@@ -252,23 +257,20 @@ def report_hook(index, blksize, size):
252257
if not isdir(target):
253258
if url.startswith('git+'):
254259
url = url[4:]
255-
# if 'version' is specified, do a shallow clone
256-
if self.version:
257-
ensure_dir(target)
258-
with current_directory(target):
259-
shprint(sh.git, 'init')
260-
shprint(sh.git, 'remote', 'add', 'origin', url)
261-
else:
262-
shprint(sh.git, 'clone', '--recursive', url, target)
263-
with current_directory(target):
264-
if self.version:
265-
shprint(sh.git, 'fetch', '--tags', '--depth', '1')
266-
shprint(sh.git, 'checkout', self.version)
267-
branch = sh.git('branch', '--show-current')
268-
if branch:
269-
shprint(sh.git, 'pull')
270-
shprint(sh.git, 'pull', '--recurse-submodules')
271-
shprint(sh.git, 'submodule', 'update', '--recursive', '--init', '--depth', '1')
260+
261+
shprint(
262+
sh.git,
263+
'clone',
264+
'--branch',
265+
self.version,
266+
'--depth',
267+
'1',
268+
'--recurse-submodules',
269+
'--shallow-submodules',
270+
url,
271+
target,
272+
)
273+
272274
return target
273275

274276
def apply_patch(self, filename, arch, build_dir=None):
@@ -375,7 +377,12 @@ def get_recipe_dir(self):
375377
# Public Recipe API to be subclassed if needed
376378

377379
def download_if_necessary(self):
380+
if self.ctx.ndk_api < self.min_ndk_api_support:
381+
error(f"In order to build '{self.name}', you must set minimum ndk api (minapi) to `{self.min_ndk_api_support}`.\n")
382+
exit(1)
383+
378384
info_main('Downloading {}'.format(self.name))
385+
379386
user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower()))
380387
if user_dir is not None:
381388
info('P4A_{}_DIR is set, skipping download for {}'.format(
@@ -919,6 +926,32 @@ def folder_name(self):
919926
name = self.name
920927
return name
921928

929+
def patch_shebang(self, _file, original_bin):
930+
_file_des = open(_file, "r")
931+
932+
try:
933+
data = _file_des.readlines()
934+
except UnicodeDecodeError:
935+
return
936+
937+
if "#!" in (line := data[0]):
938+
if line.split("#!")[-1].strip() == original_bin:
939+
return
940+
941+
info(f"Fixing sheband for '{_file}'")
942+
data.pop(0)
943+
data.insert(0, "#!" + original_bin + "\n")
944+
_file_des.close()
945+
_file_des = open(_file, "w")
946+
_file_des.write("".join(data))
947+
_file_des.close()
948+
949+
def patch_shebangs(self, path, original_bin):
950+
# set correct shebang
951+
for file in listdir(path):
952+
_file = join(path, file)
953+
self.patch_shebang(_file, original_bin)
954+
922955
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
923956
env = super().get_recipe_env(arch, with_flags_in_cc)
924957
env['PYTHONNOUSERSITE'] = '1'
@@ -1260,6 +1293,9 @@ def build_arch(self, arch):
12601293
self.install_hostpython_prerequisites(
12611294
packages=["build[virtualenv]", "pip"] + self.hostpython_prerequisites
12621295
)
1296+
python_bin_dir = join(self.hostpython_site_dir, "bin")
1297+
self.patch_shebangs(python_bin_dir, self.real_hostpython_location)
1298+
12631299
build_dir = self.get_build_dir(arch.arch)
12641300
env = self.get_recipe_env(arch, with_flags_in_cc=True)
12651301
# make build dir separately
@@ -1308,6 +1344,7 @@ def get_recipe_meson_options(self, arch):
13081344
"cpp_args": self.sanitize_flags(env["CXXFLAGS"], env["CPPFLAGS"]),
13091345
"c_link_args": self.sanitize_flags(env["LDFLAGS"]),
13101346
"cpp_link_args": self.sanitize_flags(env["LDFLAGS"]),
1347+
"fortran_link_args": self.sanitize_flags(env["LDFLAGS"]),
13111348
},
13121349
"properties": {
13131350
"needs_exe_wrapper": True,
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import os
2+
import subprocess
3+
import shutil
4+
import sh
5+
from pathlib import Path
6+
from os.path import join
7+
from pythonforandroid.recipe import Recipe
8+
from pythonforandroid.recommendations import read_ndk_version
9+
from pythonforandroid.logger import info, shprint, info_main
10+
from pythonforandroid.util import ensure_dir
11+
import hashlib
12+
13+
FLANG_FILES = {
14+
"package-flang-aarch64.tar.bz2": "775f362c758abe8d3173edc7be9ced3730ff14c64d44743017c3af7ceb0a6610",
15+
"package-flang-host.tar.bz2": "04fe24d67ee7eb5a4223299c610013585e75c56467e4b185ed929a3d17e3d077",
16+
"package-flang-x86_64.tar.bz2": "2061a0e3179f4afa55516ce3858582d25ea7b108ff762d9fb4ec8a03b49b36d2",
17+
"package-install.tar.bz2": "d37dc6a58b495807f015c7fec08a57ff95d52ad0d0553cbf573b0215d8a1707c",
18+
}
19+
20+
21+
class GFortranRecipe(Recipe):
22+
# flang support in NDK by @termux (on github)
23+
name = "fortran"
24+
toolchain_ver = 0
25+
url = "https://github.com/termux/ndk-toolchain-clang-with-flang/releases/download/"
26+
27+
def match_sha256(self, file_path, expected_hash):
28+
sha256 = hashlib.sha256()
29+
with open(file_path, "rb") as f:
30+
for chunk in iter(lambda: f.read(8192), b""):
31+
sha256.update(chunk)
32+
file_hash = sha256.hexdigest()
33+
return file_hash == expected_hash
34+
35+
@property
36+
def ndk_version(self):
37+
ndk_version = read_ndk_version(self.ctx.ndk_dir)
38+
minor_to_letter = {0: ""}
39+
minor_to_letter.update(
40+
{n + 1: chr(i) for n, i in enumerate(range(ord("b"), ord("b") + 25))}
41+
)
42+
return f"{ndk_version.major}{minor_to_letter[ndk_version.minor]}"
43+
44+
def get_cache_dir(self):
45+
dir_name = self.get_dir_name()
46+
return join(self.ctx.build_dir, "other_builds", dir_name)
47+
48+
def get_fortran_dir(self):
49+
toolchain_name = f"android-r{self.ndk_version}-api-{self.ctx.ndk_api}"
50+
return join(
51+
self.get_cache_dir(), f"{toolchain_name}-flang-v{self.toolchain_ver}"
52+
)
53+
54+
def get_incomplete_files(self):
55+
incomplete_files = []
56+
cache_dir = self.get_cache_dir()
57+
for file, sha256sum in FLANG_FILES.items():
58+
_file = join(cache_dir, file)
59+
if not (os.path.exists(_file) and self.match_sha256(_file, sha256sum)):
60+
incomplete_files.append(file)
61+
return incomplete_files
62+
63+
def download_if_necessary(self):
64+
assert self.ndk_version == "27c"
65+
if len(self.get_incomplete_files()) == 0:
66+
return
67+
self.download()
68+
69+
def download(self):
70+
cache_dir = self.get_cache_dir()
71+
ensure_dir(cache_dir)
72+
for file in self.get_incomplete_files():
73+
_file = join(cache_dir, file)
74+
if os.path.exists(_file):
75+
os.remove(_file)
76+
self.download_file(f"{self.url}r{join(self.ndk_version, file)}", _file)
77+
78+
def extract_tar(self, file_path: Path, dest: Path, strip=1):
79+
shprint(
80+
sh.tar,
81+
"xf",
82+
str(file_path),
83+
"--strip-components",
84+
str(strip),
85+
"-C",
86+
str(dest) if dest else ".",
87+
)
88+
89+
def create_flang_wrapper(self, path: Path, target: str):
90+
script = f"""#!/usr/bin/env bash
91+
if [ "$1" != "-cpp" ] && [ "$1" != "-fc1" ]; then
92+
`dirname $0`/flang-new --target={target}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api} "$@"
93+
else
94+
`dirname $0`/flang-new "$@"
95+
fi
96+
"""
97+
path.write_text(script)
98+
path.chmod(0o755)
99+
100+
def unpack(self, arch):
101+
info_main("Unpacking fortran")
102+
103+
flang_folder = self.get_fortran_dir()
104+
if os.path.exists(flang_folder):
105+
info("{} is already unpacked, skipping".format(self.name))
106+
return
107+
108+
toolchain_path = Path(
109+
join(self.ctx.ndk_dir, "toolchains/llvm/prebuilt/linux-x86_64")
110+
)
111+
cache_dir = Path(os.path.abspath(self.get_cache_dir()))
112+
113+
# clean tmp folder
114+
tmp_folder = Path(os.path.abspath(f"{flang_folder}-tmp"))
115+
shutil.rmtree(tmp_folder, ignore_errors=True)
116+
tmp_folder.mkdir(parents=True)
117+
os.chdir(tmp_folder)
118+
119+
self.extract_tar(cache_dir / "package-install.tar.bz2", None, strip=4)
120+
self.extract_tar(cache_dir / "package-flang-host.tar.bz2", None)
121+
122+
sysroot_path = tmp_folder / "sysroot"
123+
shutil.copytree(toolchain_path / "sysroot", sysroot_path)
124+
125+
self.extract_tar(
126+
cache_dir / "package-flang-aarch64.tar.bz2",
127+
sysroot_path / "usr/lib/aarch64-linux-android",
128+
)
129+
self.extract_tar(
130+
cache_dir / "package-flang-x86_64.tar.bz2",
131+
sysroot_path / "usr/lib/x86_64-linux-android",
132+
)
133+
134+
# Fix lib/clang paths
135+
version_output = subprocess.check_output(
136+
[str(tmp_folder / "bin/clang"), "--version"], text=True
137+
)
138+
clang_version = next(
139+
(line for line in version_output.splitlines() if "clang version" in line),
140+
"",
141+
)
142+
major_ver = clang_version.split("clang version ")[-1].split(".")[0]
143+
144+
lib_path = tmp_folder / f"lib/clang/{major_ver}/lib"
145+
src_lib_path = toolchain_path / f"lib/clang/{major_ver}/lib"
146+
shutil.rmtree(lib_path, ignore_errors=True)
147+
lib_path.mkdir(parents=True)
148+
149+
for item in src_lib_path.iterdir():
150+
shprint(sh.cp, "-r", str(item), str(lib_path))
151+
152+
# Create flang wrappers
153+
targets = [
154+
"aarch64-linux-android",
155+
"armv7a-linux-androideabi",
156+
"i686-linux-android",
157+
"x86_64-linux-android",
158+
]
159+
160+
for target in targets:
161+
wrapper_path = tmp_folder / f"bin/{target}-flang"
162+
self.create_flang_wrapper(wrapper_path, target)
163+
shutil.copy(
164+
wrapper_path, tmp_folder / f"bin/{target}{self.ctx.ndk_api}-flang"
165+
)
166+
167+
tmp_folder.rename(flang_folder)
168+
169+
@property
170+
def bin_path(self):
171+
return f"{self.get_fortran_dir()}/bin"
172+
173+
def get_host_platform(self, arch):
174+
return {
175+
"arm64-v8a": "aarch64-linux-android",
176+
"armeabi-v7a": "armv7a-linux-androideabi",
177+
"x86_64": "x86_64-linux-android",
178+
"x86": "i686-linux-android",
179+
}[arch]
180+
181+
def get_fortran_bin(self, arch):
182+
return join(self.bin_path, f"{self.get_host_platform(arch)}-flang")
183+
184+
def get_fortran_flags(self, arch):
185+
return f"--target={self.get_host_platform(arch)}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api}"
186+
187+
188+
recipe = GFortranRecipe()

0 commit comments

Comments
 (0)