Skip to content

Commit 95fa278

Browse files
authored
Merge branch 'develop' into no-distutils
2 parents 5844ca7 + 9720dbd commit 95fa278

File tree

22 files changed

+682
-1407
lines changed

22 files changed

+682
-1407
lines changed

ci/constants.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ 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',
28+
# GitHub CI runs out of storage while building it
29+
'scipy',
30+
'fortran',
3031
# Outdated and there's a chance that is now useless.
3132
'zope_interface',
3233
# Requires zope_interface, which is broken.

pythonforandroid/archs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def get_env(self, with_flags_in_cc=True):
132132
env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(
133133
ctx=self.ctx,
134134
command_prefix=self.command_prefix,
135-
python_includes=join(self.ctx.python_recipe.get_build_dir(self.arch), 'Include')
135+
python_includes=join(Recipe.get_recipe("python3", self.ctx).include_root(self.arch))
136136
)
137137

138138
# LDFLAGS: Link the extra global link paths first before anything else

pythonforandroid/bootstraps/common/build/jni/application/src/start.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ int main(int argc, char *argv[]) {
281281
" self.__buffer = lines[-1]\n"
282282
"sys.stdout = sys.stderr = LogFile()\n"
283283
"print('Android path', sys.path)\n"
284-
"import os\n"
285-
"print('os.environ is', os.environ)\n"
284+
"# import os\n"
285+
"# print('os.environ is', os.environ)\n"
286286
"print('Android kivy bootstrap done. __name__ is', __name__)");
287287

288288
#if PY_MAJOR_VERSION < 3

pythonforandroid/recipe.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
977977
self.patch_shebangs(self._host_recipe.local_bin, self.real_hostpython_location)
978978
env["PATH"] = self._host_recipe.local_bin + ":" + self._host_recipe.site_bin + ":" + env["PATH"]
979979

980-
host_env = self.get_hostrecipe_env()
980+
host_env = self.get_hostrecipe_env(arch)
981981
env['PYTHONPATH'] = host_env["PYTHONPATH"]
982982

983983
if not self.call_hostpython_via_targetpython:
@@ -1341,6 +1341,10 @@ class MesonRecipe(PyProjectRecipe):
13411341
meson_version = "1.4.0"
13421342
ninja_version = "1.11.1.1"
13431343

1344+
skip_python = False
1345+
'''If true, skips all Python build and installation steps.
1346+
Useful for Meson projects written purely in C/C++ without Python bindings.'''
1347+
13441348
def sanitize_flags(self, *flag_strings):
13451349
return " ".join(flag_strings).strip().split(" ")
13461350

@@ -1358,6 +1362,7 @@ def get_recipe_meson_options(self, arch):
13581362
"cpp_args": self.sanitize_flags(env["CXXFLAGS"], env["CPPFLAGS"]),
13591363
"c_link_args": self.sanitize_flags(env["LDFLAGS"]),
13601364
"cpp_link_args": self.sanitize_flags(env["LDFLAGS"]),
1365+
"fortran_link_args": self.sanitize_flags(env["LDFLAGS"]),
13611366
},
13621367
"properties": {
13631368
"needs_exe_wrapper": True,
@@ -1422,7 +1427,8 @@ def build_arch(self, arch):
14221427
]:
14231428
if dep not in self.hostpython_prerequisites:
14241429
self.hostpython_prerequisites.append(dep)
1425-
super().build_arch(arch)
1430+
if not self.skip_python:
1431+
super().build_arch(arch)
14261432

14271433

14281434
class RustCompiledComponentsRecipe(PyProjectRecipe):

pythonforandroid/recipes/android/src/android/broadcast.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# -------------------------------------------------------------------
22
# Broadcast receiver bridge
3-
3+
import logging
44
from jnius import autoclass, PythonJavaClass, java_method
55
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
66

7+
logger = logging.getLogger("BroadcastReceiver")
8+
logger.setLevel(logging.DEBUG)
9+
710

811
class BroadcastReceiver(object):
912

@@ -22,6 +25,7 @@ def onReceive(self, context, intent):
2225
def __init__(self, callback, actions=None, categories=None):
2326
super().__init__()
2427
self.callback = callback
28+
self._is_registered = False
2529

2630
if not actions and not categories:
2731
raise Exception('You need to define at least actions or categories')
@@ -58,15 +62,36 @@ def _expand_partial_name(partial_name):
5862
self.receiver_filter.addCategory(x)
5963

6064
def start(self):
61-
Handler = autoclass('android.os.Handler')
65+
66+
if hasattr(self, 'handlerthread') and self.handlerthread.isAlive():
67+
logger.debug("HandlerThread already running, skipping start")
68+
return
69+
70+
HandlerThread = autoclass('android.os.HandlerThread')
71+
self.handlerthread = HandlerThread('handlerthread')
6272
self.handlerthread.start()
73+
74+
if self._is_registered:
75+
logger.info("Already registered.")
76+
return
77+
78+
Handler = autoclass('android.os.Handler')
6379
self.handler = Handler(self.handlerthread.getLooper())
6480
self.context.registerReceiver(
6581
self.receiver, self.receiver_filter, None, self.handler)
82+
self._is_registered = True
6683

6784
def stop(self):
68-
self.context.unregisterReceiver(self.receiver)
69-
self.handlerthread.quit()
85+
try:
86+
self.context.unregisterReceiver(self.receiver)
87+
self._is_registered = False
88+
except Exception as e:
89+
logger.error("unregisterReceiver failed: %s", e)
90+
91+
if hasattr(self, 'handlerthread'):
92+
self.handlerthread.quitSafely()
93+
self.handlerthread = None
94+
self.handler = None
7095

7196
@property
7297
def context(self):
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": "bf01399513e3b435224d9a9f656b72a0965a23fdd8c3c26af0f7c32f2a5f3403",
15+
"package-flang-host.tar.bz2": "3ea2c0e8125ededddf9b3f23c767b8e37816e140ac934c76ace19a168fefdf83",
16+
"package-flang-x86_64.tar.bz2": "afe7e391355c71e7b0c8ee71a3002e83e2e524ad61810238815facf3030be6e6",
17+
"package-install.tar.bz2": "169b75f6125dc7b95e1d30416147a05d135da6cbe9cc8432d48f5b8633ac38db",
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 == "28c"
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)