Skip to content

Commit 260302e

Browse files
committed
fix numpy build
1 parent 170556c commit 260302e

5 files changed

Lines changed: 145 additions & 12 deletions

File tree

pythonforandroid/meson_python.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import sys
2+
import json
3+
import subprocess
4+
import importlib.util
5+
from os.path import join
6+
from glob import glob
7+
8+
9+
class C:
10+
TARGET_PYTHON_PREFIX = globals().get("TARGET_PYTHON_PREFIX")
11+
PYTHON_MAJOR_VERSION = globals().get("PYTHON_MAJOR_VERSION", sys.version_info.major)
12+
PYTHON_MINOR_VERSION = globals().get("PYTHON_MINOR_VERSION", sys.version_info.minor)
13+
PLATFORM_TAG = globals().get("PLATFORM_TAG", sys.platform)
14+
PYTHON_SUFFIX = globals().get("PYTHON_SUFFIX", "")
15+
16+
17+
def handle_python_info():
18+
19+
prefix = C.TARGET_PYTHON_PREFIX
20+
21+
sysconfig_file = glob(join(prefix, "lib/python3.14/_sysconfigdata*.py"))[0]
22+
23+
spec = importlib.util.spec_from_file_location("_android_cfg", sysconfig_file)
24+
cfg = importlib.util.module_from_spec(spec)
25+
spec.loader.exec_module(cfg)
26+
27+
ver_python = f"python{C.PYTHON_MAJOR_VERSION}.{C.PYTHON_MINOR_VERSION}"
28+
29+
site_path = join(prefix, f"lib/{ver_python}/site-packages")
30+
31+
android_paths = {
32+
"stdlib": join(prefix, "lib"),
33+
"platstdlib": join(prefix, "lib"),
34+
"purelib": site_path,
35+
"platlib": site_path,
36+
"include": join(prefix, f"include/{ver_python}/"),
37+
"platinclude": join(prefix, f"include/{ver_python}/"),
38+
"scripts": join(prefix, "bin"),
39+
"data": prefix,
40+
}
41+
42+
print(
43+
json.dumps(
44+
{
45+
"variables": cfg.build_time_vars,
46+
"paths": android_paths,
47+
"sysconfig_paths": android_paths,
48+
"install_paths": android_paths,
49+
"version": f"{C.PYTHON_MAJOR_VERSION}.{C.PYTHON_MINOR_VERSION}",
50+
"platform": C.PLATFORM_TAG,
51+
"is_pypy": False,
52+
"is_venv": False,
53+
"link_libpython": True,
54+
"suffix": C.PYTHON_SUFFIX,
55+
"limited_api_suffix": ".abi3.so",
56+
"is_freethreaded": False,
57+
}
58+
)
59+
)
60+
61+
62+
WHITELIST = {
63+
"python_info.py": handle_python_info,
64+
}
65+
66+
67+
args = sys.argv[1:]
68+
69+
if args:
70+
cmd = args[0]
71+
name = cmd.split("/")[-1]
72+
73+
if name in WHITELIST:
74+
sys.exit(WHITELIST[name]())
75+
76+
sys.exit(subprocess.run([sys.executable] + args).returncode)

pythonforandroid/recipe.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import zipfile
1111
import urllib.request
1212
from urllib.request import urlretrieve
13-
from os import listdir, unlink, environ, curdir, walk
13+
from os import listdir, unlink, environ, curdir, walk, chmod
1414
from sys import stdout
15+
from packaging.version import Version
1516
from multiprocessing import cpu_count
1617
import time
1718
try:
@@ -1058,6 +1059,7 @@ def install_hostpython_prerequisites(self, packages=None, force_upgrade=True):
10581059
pip_options = [
10591060
"install",
10601061
*packages,
1062+
"-q",
10611063
]
10621064
if force_upgrade:
10631065
pip_options.append("--upgrade")
@@ -1303,6 +1305,8 @@ def get_recipe_env(self, arch, **kwargs):
13031305
file.close()
13041306

13051307
env["DIST_EXTRA_CONFIG"] = build_opts
1308+
python_recipe = Recipe.get_recipe("python3", self.ctx)
1309+
env["INCLUDEPY"] = python_recipe.include_root(arch.arch)
13061310
return env
13071311

13081312
@staticmethod
@@ -1374,7 +1378,6 @@ def build_arch(self, arch):
13741378
if not (isfile(join(build_dir, "pyproject.toml")) or isfile(join(build_dir, "setup.py"))):
13751379
warning("Skipping build because it does not appear to be a Python project.")
13761380
return
1377-
13781381
self.install_hostpython_prerequisites(
13791382
packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites
13801383
)
@@ -1405,7 +1408,6 @@ class MesonRecipe(PyProjectRecipe):
14051408
'''Recipe for projects which uses meson as build system'''
14061409

14071410
meson_version = "1.4.0"
1408-
ninja_version = "1.11.1.1"
14091411

14101412
skip_python = False
14111413
'''If true, skips all Python build and installation steps.
@@ -1414,10 +1416,55 @@ class MesonRecipe(PyProjectRecipe):
14141416
def sanitize_flags(self, *flag_strings):
14151417
return " ".join(flag_strings).strip().split(" ")
14161418

1419+
def get_python_wrapper(self, arch):
1420+
"""
1421+
Meson Python introspection runs on the host interpreter, but the
1422+
target Python (Android) cannot be executed on the build machine.
1423+
1424+
We therefore run host Python and override sysconfig data to emulate
1425+
the target Android Python environment during Meson introspection.
1426+
"""
1427+
python_recipe = Recipe.get_recipe('python3', self.ctx)
1428+
target_prefix = python_recipe.get_python_root(arch)
1429+
python_file = join(self.ctx.root_dir, 'meson_python.py')
1430+
_arch = {
1431+
"arm64-v8a": ["aarch64"],
1432+
"x86_64": ["x86_64"],
1433+
"armeabi-v7a": ["arm"],
1434+
"x86": ["i686"],
1435+
}[arch.arch][0]
1436+
1437+
# Real values pulled from android
1438+
# PYTHON_MAJOR_VERSION -> 3
1439+
# PYTHON_MINOR_VERSION -> 14
1440+
# PLATFORM_TAG eg -> 'android-24-arm64_v8a'
1441+
# PYTHON_SUFFIX eg -> '.cpython-314-aarch64-linux-android.so'
1442+
1443+
_p_version = Version(python_recipe.version)
1444+
file_data = f"#!{self.real_hostpython_location}"
1445+
file_data += f"\nTARGET_PYTHON_PREFIX='{target_prefix}'"
1446+
file_data += f"\nPYTHON_MAJOR_VERSION='{_p_version.major}'"
1447+
file_data += f"\nPYTHON_MINOR_VERSION='{_p_version.minor}'"
1448+
file_data += f"\nPLATFORM_TAG='{self.get_wheel_platform_tags(arch.arch, self.ctx)[0]}'"
1449+
file_data += f"\nPYTHON_SUFFIX='.cpython-{_p_version.major}{_p_version.minor}-{_arch}-linux-android.so'"
1450+
1451+
with open(python_file, "r") as f:
1452+
file_data += "\n" + f.read()
1453+
1454+
wrapper_dir = join(self.get_build_dir(arch.arch), "p4a_python_wrapper")
1455+
ensure_dir(wrapper_dir)
1456+
wrapper_path = join(wrapper_dir, "python")
1457+
with open(wrapper_path, "w") as f:
1458+
f.write(file_data)
1459+
chmod(wrapper_path, 0o755)
1460+
1461+
return wrapper_path
1462+
14171463
def get_recipe_meson_options(self, arch):
14181464
env = self.get_recipe_env(arch, with_flags_in_cc=True)
14191465
return {
14201466
"binaries": {
1467+
"python": self.get_python_wrapper(arch),
14211468
"c": arch.get_clang_exe(with_target=True),
14221469
"cpp": arch.get_clang_exe(with_target=True, plus_plus=True),
14231470
"ar": self.ctx.ndk.llvm_ar,
@@ -1488,13 +1535,18 @@ def build_arch(self, arch):
14881535
self.ensure_args('-Csetup-args=--cross-file', '-Csetup-args={}'.format(cross_file))
14891536
# ensure ninja and meson
14901537
for dep in [
1491-
"ninja=={}".format(self.ninja_version),
1538+
"ninja",
14921539
"meson=={}".format(self.meson_version),
14931540
]:
14941541
if dep not in self.hostpython_prerequisites:
14951542
self.hostpython_prerequisites.append(dep)
1543+
14961544
if not self.skip_python:
14971545
super().build_arch(arch)
1546+
else:
1547+
self.install_hostpython_prerequisites(
1548+
packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites
1549+
)
14981550

14991551

15001552
class RustCompiledComponentsRecipe(PyProjectRecipe):

pythonforandroid/recipes/matplotlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class MatplotlibRecipe(MesonRecipe):
1212
"pyparsing",
1313
"python-dateutil",
1414
]
15+
hostpython_prerequisites = ["meson-python>=0.13.1,<0.17.0", "pybind11>=2.13.2,!=2.13.3", "setuptools_scm>=7"]
1516
need_stl_shared = True
1617

1718
def get_recipe_env(self, arch, **kwargs):

pythonforandroid/recipes/python3/__init__.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class Python3Recipe(TargetPythonRecipe):
8282

8383
# Android prefix
8484
'--prefix={prefix}',
85-
'--exec-prefix={exec_prefix}',
8685
'--enable-loadable-sqlite-extensions',
8786

8887
# Special cross compile args
@@ -211,11 +210,17 @@ def apply_patches(self, arch, build_dir=None):
211210
super().apply_patches(arch, build_dir)
212211

213212
def include_root(self, arch_name):
214-
return join(self.get_build_dir(arch_name), 'Include')
213+
return glob.glob(join(self.get_build_dir(arch_name), 'android-build', 'android-root', 'include/*/'))[0]
215214

216215
def link_root(self, arch_name):
217216
return join(self.get_build_dir(arch_name), 'android-build')
218217

218+
def get_python_root(self, arch):
219+
return join(self.get_build_dir(arch.arch), 'android-build', 'android-root')
220+
221+
def get_android_python_exe(self, arch):
222+
return join(self.get_python_root(arch), 'bin', self.name)
223+
219224
def should_build(self, arch):
220225
return not isfile(join(self.link_root(arch.arch), self._libpython))
221226

@@ -342,9 +347,8 @@ def build_arch(self, arch):
342347
build_dir = join(recipe_build_dir, 'android-build')
343348
ensure_dir(build_dir)
344349

345-
# TODO: Get these dynamically, like bpo-30386 does
346-
sys_prefix = '/usr/local'
347-
sys_exec_prefix = '/usr/local'
350+
sys_prefix = join(build_dir, "android-root")
351+
ensure_dir(sys_prefix)
348352

349353
env = self.get_recipe_env(arch)
350354
env = self.set_libs_flags(env, arch)
@@ -363,8 +367,7 @@ def build_arch(self, arch):
363367
python_host_bin=self.get_recipe(
364368
'host' + self.name, self.ctx
365369
).python_exe,
366-
prefix=sys_prefix,
367-
exec_prefix=sys_exec_prefix)).split(' '),
370+
prefix=sys_prefix).split(' ')),
368371
_env=env)
369372

370373
shprint(
@@ -373,6 +376,8 @@ def build_arch(self, arch):
373376
'INSTSONAME={lib_name}'.format(lib_name=self._libpython),
374377
_env=env
375378
)
379+
shprint(sh.make, 'install', _env=env)
380+
376381
# rename executable
377382
if isfile("python"):
378383
sh.cp('python', 'libpythonbin.so')

pythonforandroid/recipes/scipy/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class ScipyRecipe(MesonRecipe):
1717

1818
def get_recipe_meson_options(self, arch):
1919
options = super().get_recipe_meson_options(arch)
20-
options["binaries"]["python"] = self.ctx.python_recipe.python_exe
2120
options["binaries"]["fortran"] = self.place_wrapper(arch)
2221
options["properties"]["numpy-include-dir"] = join(
2322
self.ctx.get_python_install_dir(arch.arch), "numpy/_core/include"

0 commit comments

Comments
 (0)