From 1bb30bef5f4d66de9e1369e89084c0b8bcbe28e7 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Thu, 16 Apr 2026 16:45:41 +0530 Subject: [PATCH 01/11] fix PYTHONPATH hacks --- pythonforandroid/build.py | 4 - pythonforandroid/recipe.py | 19 +--- .../recipes/hostpython3/__init__.py | 88 ++++++++++--------- pythonforandroid/recipes/python3/__init__.py | 4 +- tests/recipes/test_hostpython3.py | 6 -- 5 files changed, 54 insertions(+), 67 deletions(-) diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index c8259c8bb0..d88fdb1fef 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -728,10 +728,6 @@ def process_python_modules(ctx, modules, arch): host_recipe = None try: host_recipe = Recipe.get_recipe("hostpython3", ctx) - _python_path = host_recipe.get_path_to_python() - libdir = glob.glob(join(_python_path, "build", "lib*")) - env['PYTHONPATH'] = host_recipe.site_dir + ":" + join( - _python_path, "Modules") + ":" + (libdir[0] if libdir else "") pip = host_recipe.pip except Exception: # hostpython3 is unavailable, so fall back to system pip diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 6aa558778e..ead38cee56 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -1031,10 +1031,8 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True): def get_hostrecipe_env(self, arch=None): env = environ.copy() - _python_path = self._host_recipe.get_path_to_python() - libdir = glob.glob(join(_python_path, "build", "lib*")) - env['PYTHONPATH'] = self._host_recipe.site_dir + ":" + join( - _python_path, "Modules") + ":" + (libdir[0] if libdir else "") + env['PYTHONPATH'] = '' + env['HOME'] = '/tmp' return env @property @@ -1063,14 +1061,9 @@ def install_hostpython_prerequisites(self, packages=None, force_upgrade=True): pip_options = [ "install", *packages, - "--target", self._host_recipe.site_dir, "--python-version", - self.ctx.python_recipe.version, - # Don't use sources, instead wheels - "--only-binary=:all:", ] if force_upgrade: pip_options.append("--upgrade") - # Use system's pip pip_env = self.get_hostrecipe_env() shprint(self._host_recipe.pip, *pip_options, _env=pip_env) @@ -1397,8 +1390,6 @@ def build_arch(self, arch): # make build dir separately sub_build_dir = join(build_dir, "p4a_android_build") ensure_dir(sub_build_dir) - # copy hostpython to built python to ensure correct selection of libs and includes - shprint(sh.cp, self.real_hostpython_location, self.ctx.python_recipe.python_exe) build_args = [ "-m", @@ -1411,7 +1402,7 @@ def build_arch(self, arch): built_wheels = [] with current_directory(build_dir): shprint( - sh.Command(self.ctx.python_recipe.python_exe), *build_args, _env=env + sh.Command(self.real_hostpython_location), *build_args, _env=env ) built_wheels = [realpath(whl) for whl in glob.glob("dist/*.whl")] self.install_wheel(arch, built_wheels) @@ -1523,8 +1514,6 @@ class RustCompiledComponentsRecipe(PyProjectRecipe): "x86": "i686-linux-android", } - call_hostpython_via_targetpython = False - def get_recipe_env(self, arch, **kwargs): env = super().get_recipe_env(arch, **kwargs) @@ -1563,7 +1552,7 @@ def get_recipe_env(self, arch, **kwargs): env["PATH"] = ("{hostpython_dir}:{old_path}").format( hostpython_dir=Recipe.get_recipe( "hostpython3", self.ctx - ).get_path_to_python(), + ).local_bin, old_path=env["PATH"], ) return env diff --git a/pythonforandroid/recipes/hostpython3/__init__.py b/pythonforandroid/recipes/hostpython3/__init__.py index 7dc28d00f1..e44dba605f 100644 --- a/pythonforandroid/recipes/hostpython3/__init__.py +++ b/pythonforandroid/recipes/hostpython3/__init__.py @@ -15,17 +15,13 @@ ) from pythonforandroid.prerequisites import OpenSSLPrerequisite -HOSTPYTHON_VERSION_UNSET_MESSAGE = ( - 'The hostpython recipe must have set version' -) +HOSTPYTHON_VERSION_UNSET_MESSAGE = "The hostpython recipe must have set version" -SETUP_DIST_NOT_FIND_MESSAGE = ( - 'Could not find Setup.dist or Setup in Python build' -) +SETUP_DIST_NOT_FIND_MESSAGE = "Could not find Setup.dist or Setup in Python build" class HostPython3Recipe(Recipe): - ''' + """ The hostpython3's recipe. .. versionchanged:: 2019.10.06.post0 @@ -34,17 +30,17 @@ class HostPython3Recipe(Recipe): .. versionchanged:: 0.6.0 Refactored into the new class :class:`~pythonforandroid.python.HostPythonRecipe` - ''' + """ - version = '3.14.2' + version = "3.14.2" - url = 'https://github.com/python/cpython/archive/refs/tags/v{version}.tar.gz' - '''The default url to download our host python recipe. This url will - change depending on the python version set in attribute :attr:`version`.''' + url = "https://github.com/python/cpython/archive/refs/tags/v{version}.tar.gz" + """The default url to download our host python recipe. This url will + change depending on the python version set in attribute :attr:`version`.""" - build_subdir = 'native-build' - '''Specify the sub build directory for the hostpython3 recipe. Defaults - to ``native-build``.''' + build_subdir = "native-build" + """Specify the sub build directory for the hostpython3 recipe. Defaults + to ``native-build``.""" patches = ["fix_ensurepip.patch"] @@ -59,17 +55,17 @@ def download(self): @property def _exe_name(self): - ''' + """ Returns the name of the python executable depending on the version. - ''' + """ if not self.version: raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE) - return f'python{self.version.split(".")[0]}' + return "python" @property def python_exe(self): - '''Returns the full path of the hostpython executable.''' - return join(self.get_path_to_python(), self._exe_name) + """Returns the full path of the hostpython executable.""" + return join(self.local_bin, self._exe_name) def get_recipe_env(self, arch=None): env = os.environ.copy() @@ -91,14 +87,14 @@ def should_build(self, arch): def get_build_container_dir(self, arch=None): choices = self.check_recipe_choices() - dir_name = '-'.join([self.name] + choices) - return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') + dir_name = "-".join([self.name] + choices) + return join(self.ctx.build_dir, "other_builds", dir_name, "desktop") def get_build_dir(self, arch=None): - ''' + """ .. note:: Unlike other recipes, the hostpython build dir doesn't depend on the target arch - ''' + """ return join(self.get_build_container_dir(), self.name) def get_path_to_python(self): @@ -112,16 +108,20 @@ def site_root(self): def site_bin(self): return join(self.site_root, self.site_dir, "bin") + @property + def local_dir(self): + return join(self.site_root, "usr/local/") + @property def local_bin(self): - return join(self.site_root, "usr/local/bin/") + return join(self.local_dir, "bin") @property def site_dir(self): p_version = Version(self.version) return join( self.site_root, - f"usr/local/lib/python{p_version.major}.{p_version.minor}/site-packages/" + f"usr/local/lib/python{p_version.major}.{p_version.minor}/site-packages/", ) @property @@ -163,34 +163,40 @@ def build_arch(self, arch): # Configure the build build_configured = False with current_directory(build_dir): - if not Path('config.status').exists(): - shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env) + if not Path("config.status").exists(): + shprint( + sh.Command(join(recipe_build_dir, "configure")), + "--prefix", + self.local_dir, + _env=env, + ) build_configured = True with current_directory(recipe_build_dir): # Create the Setup file. This copying from Setup.dist is # the normal and expected procedure before Python 3.8, but # after this the file with default options is already named "Setup" - setup_dist_location = join('Modules', 'Setup.dist') + setup_dist_location = join("Modules", "Setup.dist") if Path(setup_dist_location).exists(): - shprint(sh.cp, setup_dist_location, - join(build_dir, 'Modules', 'Setup')) + shprint(sh.cp, setup_dist_location, join(build_dir, "Modules", "Setup")) else: # Check the expected file does exist - setup_location = join('Modules', 'Setup') + setup_location = join("Modules", "Setup") if not Path(setup_location).exists(): - raise BuildInterruptingException( - SETUP_DIST_NOT_FIND_MESSAGE - ) + raise BuildInterruptingException(SETUP_DIST_NOT_FIND_MESSAGE) - shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env) + shprint(sh.make, "-j", str(cpu_count()), "-C", build_dir, _env=env) + with current_directory(build_dir): + shprint(sh.make, "install", _env=env) + + with current_directory(recipe_build_dir): # make a copy of the python executable giving it the name we want, # because we got different python's executable names depending on # the fs being case-insensitive (Mac OS X, Cygwin...) or # case-sensitive (linux)...so this way we will have an unique name # for our hostpython, regarding the used fs - for exe_name in ['python.exe', 'python']: + for exe_name in ["python.exe", "python"]: exe = join(self.get_path_to_python(), exe_name) if Path(exe).is_file(): shprint(sh.cp, exe, self.python_exe) @@ -200,10 +206,12 @@ def build_arch(self, arch): self.ctx.hostpython = self.python_exe if build_configured: - shprint( - sh.Command(self.python_exe), "-m", "ensurepip", "--root", self.site_root, "-U", - _env={"HOME": "/tmp", "PATH": self.local_bin} + sh.Command(self.python_exe), + "-m", + "ensurepip", + "-U", + _env={"HOME": "/tmp", "PATH": self.local_bin}, ) self.fix_pip_shebangs() diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index e36e0b28ad..a5b60a2495 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -360,9 +360,9 @@ def build_arch(self, arch): *(' '.join(self.configure_args).format( android_host=env['HOSTARCH'], android_build=android_build, - python_host_bin=join(self.get_recipe( + python_host_bin=self.get_recipe( 'host' + self.name, self.ctx - ).get_path_to_python(), "python3"), + ).python_exe, prefix=sys_prefix, exec_prefix=sys_exec_prefix)).split(' '), _env=env) diff --git a/tests/recipes/test_hostpython3.py b/tests/recipes/test_hostpython3.py index df0c5cac05..ba05f3c144 100644 --- a/tests/recipes/test_hostpython3.py +++ b/tests/recipes/test_hostpython3.py @@ -29,12 +29,6 @@ def test_property__exe_name_no_version(self): def test_property__exe_name(self): self.assertEqual(self.recipe._exe_name, 'python3') - def test_property_python_exe(self): - self.assertEqual( - self.recipe.python_exe, - join(self.recipe.get_path_to_python(), 'python3') - ) - @mock.patch("pythonforandroid.recipes.hostpython3.Path.exists") def test_should_build(self, mock_exists): # test case for existing python exe which shouldn't trigger the build From 3035ca5979c3511ec5be09b43010e7ab52d53b58 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Fri, 17 Apr 2026 11:45:43 +0530 Subject: [PATCH 02/11] fix android recipe --- pythonforandroid/recipe.py | 5 +++-- pythonforandroid/recipes/android/src/pyproject.toml | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 pythonforandroid/recipes/android/src/pyproject.toml diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index ead38cee56..d2adcb61b5 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -966,7 +966,8 @@ def patch_shebangs(self, path, original_bin): # set correct shebang for file in listdir(path): _file = join(path, file) - self.patch_shebang(_file, original_bin) + if isfile(_file): + self.patch_shebang(_file, original_bin) def get_recipe_env(self, arch=None, with_flags_in_cc=True): if self._host_recipe is None: @@ -1384,7 +1385,7 @@ def build_arch(self, arch): self.install_hostpython_prerequisites( packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites ) - self.patch_shebangs(self._host_recipe.site_bin, self.real_hostpython_location) + self.patch_shebangs(self._host_recipe.local_bin, self.real_hostpython_location) env = self.get_recipe_env(arch, with_flags_in_cc=True) # make build dir separately diff --git a/pythonforandroid/recipes/android/src/pyproject.toml b/pythonforandroid/recipes/android/src/pyproject.toml new file mode 100644 index 0000000000..bb5f8b4fa4 --- /dev/null +++ b/pythonforandroid/recipes/android/src/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = [ + "setuptools", + "wheel", + "Cython>=0.29,<3.1" +] +build-backend = "setuptools.build_meta" From d69ec311787d44e6a95369e0f4b5af5e07429c0e Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Fri, 17 Apr 2026 14:03:22 +0530 Subject: [PATCH 03/11] fix numpy and matplotlib build --- pythonforandroid/logger.py | 13 ++++++++ pythonforandroid/recipe.py | 5 +-- .../recipes/matplotlib/__init__.py | 28 +++++++--------- .../recipes/matplotlib/meson.patch | 21 ------------ pythonforandroid/recipes/numpy/__init__.py | 32 +++++++++---------- 5 files changed, 40 insertions(+), 59 deletions(-) delete mode 100644 pythonforandroid/recipes/matplotlib/meson.patch diff --git a/pythonforandroid/logger.py b/pythonforandroid/logger.py index 8bcf85c2ee..9f09a20947 100644 --- a/pythonforandroid/logger.py +++ b/pythonforandroid/logger.py @@ -135,6 +135,14 @@ def shprint(command, *args, **kwargs): kwargs["_out_bufsize"] = 1 kwargs["_err_to_out"] = True kwargs["_bg"] = True + + # silent mode + silent = kwargs.get("silent", False) + kwargs.pop("silent", False) + if silent: + kwargs["_out"] = None + kwargs["_err"] = None + is_critical = kwargs.pop('_critical', False) tail_n = kwargs.pop('_tail', None) full_debug = False @@ -190,6 +198,11 @@ def shprint(command, *args, **kwargs): Err_Style.RESET_ALL, ' ', width=(columns - 1))) stdout.flush() except sh.ErrorReturnCode as err: + if silent: + if is_critical: + exit(1) + else: + raise if need_closing_newline: stdout.write('{}\r{:>{width}}\r'.format( Err_Style.RESET_ALL, ' ', width=(columns - 1))) diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index d2adcb61b5..74eabbd086 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -1278,7 +1278,7 @@ def lookup_prebuilt(self, arch): pip_options.extend(["--dry-run", "-q"]) pip_env = self.get_hostrecipe_env() try: - shprint(self._host_recipe.pip, *pip_options, _env=pip_env) + shprint(self._host_recipe.pip, *pip_options, _env=pip_env, silent=True) except Exception: return False return True @@ -1295,9 +1295,6 @@ def check_prebuilt(self, arch, msg=""): return False def get_recipe_env(self, arch, **kwargs): - # Custom hostpython - self.ctx.python_recipe.python_exe = join( - self.ctx.python_recipe.get_build_dir(arch), "android-build", "python3") env = super().get_recipe_env(arch, **kwargs) build_dir = self.get_build_dir(arch) ensure_dir(build_dir) diff --git a/pythonforandroid/recipes/matplotlib/__init__.py b/pythonforandroid/recipes/matplotlib/__init__.py index f3ac80bddc..2213499efe 100644 --- a/pythonforandroid/recipes/matplotlib/__init__.py +++ b/pythonforandroid/recipes/matplotlib/__init__.py @@ -1,29 +1,23 @@ from pythonforandroid.recipe import MesonRecipe -from pythonforandroid.logger import shprint - -from os.path import join -import sh class MatplotlibRecipe(MesonRecipe): - version = '3.10.7' - url = 'https://github.com/matplotlib/matplotlib/archive/v{version}.zip' - depends = ['kiwisolver', 'numpy', 'pillow'] - python_depends = ['cycler', 'fonttools', 'packaging', 'pyparsing', 'python-dateutil'] - hostpython_prerequisites = ["setuptools_scm>=7"] - patches = ["meson.patch"] + version = "3.10.7" + url = "https://github.com/matplotlib/matplotlib/archive/v{version}.zip" + depends = ["kiwisolver", "numpy", "pillow"] + python_depends = [ + "cycler", + "fonttools", + "packaging", + "pyparsing", + "python-dateutil", + ] need_stl_shared = True def get_recipe_env(self, arch, **kwargs): env = super().get_recipe_env(arch, **kwargs) - env['CXXFLAGS'] += ' -Wno-c++11-narrowing' + env["CXXFLAGS"] += " -Wno-c++11-narrowing" return env - def build_arch(self, arch): - python_path = join(self.ctx.python_recipe.get_build_dir(arch), "android-build", "python3") - self.extra_build_args += [f'-Csetup-args=-Dpython3_program={python_path}'] - shprint(sh.cp, self.real_hostpython_location, python_path) - super().build_arch(arch) - recipe = MatplotlibRecipe() diff --git a/pythonforandroid/recipes/matplotlib/meson.patch b/pythonforandroid/recipes/matplotlib/meson.patch deleted file mode 100644 index babab09002..0000000000 --- a/pythonforandroid/recipes/matplotlib/meson.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff '--color=auto' -uNr matplotlib-3.10.7/meson.build matplotlib-3.10.7.mod/meson.build ---- matplotlib-3.10.7/meson.build 2025-10-09 04:16:31.000000000 +0530 -+++ matplotlib-3.10.7.mod/meson.build 2025-10-12 10:19:29.664280049 +0530 -@@ -36,7 +36,7 @@ - - # https://mesonbuild.com/Python-module.html - py_mod = import('python') --py3 = py_mod.find_installation(pure: false) -+py3 = py_mod.find_installation(get_option('python3_program'), pure: false) - py3_dep = py3.dependency() - - pybind11_dep = dependency('pybind11', version: '>=2.13.2') -diff '--color=auto' -uNr matplotlib-3.10.7/meson.options matplotlib-3.10.7.mod/meson.options ---- matplotlib-3.10.7/meson.options 2025-10-09 04:16:31.000000000 +0530 -+++ matplotlib-3.10.7.mod/meson.options 2025-10-12 10:19:23.762042521 +0530 -@@ -28,3 +28,5 @@ - # default is determined by fallback. - option('rcParams-backend', type: 'string', value: 'auto', - description: 'Set default backend at runtime') -+ -+option('python3_program', type : 'string', value : '', description : 'Path to Python 3 executable') diff --git a/pythonforandroid/recipes/numpy/__init__.py b/pythonforandroid/recipes/numpy/__init__.py index 140ff849d8..4da77b769c 100644 --- a/pythonforandroid/recipes/numpy/__init__.py +++ b/pythonforandroid/recipes/numpy/__init__.py @@ -2,24 +2,23 @@ from os.path import join import shutil -NUMPY_NDK_MESSAGE = "In order to build numpy, you must set minimum ndk api (minapi) to `24`.\n" +NUMPY_NDK_MESSAGE = ( + "In order to build numpy, you must set minimum ndk api (minapi) to `24`.\n" +) class NumpyRecipe(MesonRecipe): - version = 'v2.3.0' - url = 'git+https://github.com/numpy/numpy' - hostpython_prerequisites = ["Cython>=3.0.6", "numpy"] # meson does not detects venv's cython - extra_build_args = ['-Csetup-args=-Dblas=none', '-Csetup-args=-Dlapack=none'] + version = "v2.3.0" + url = "git+https://github.com/numpy/numpy" + extra_build_args = ["-Csetup-args=-Dblas=none", "-Csetup-args=-Dlapack=none"] need_stl_shared = True min_ndk_api_support = 24 def get_recipe_meson_options(self, arch): options = super().get_recipe_meson_options(arch) - # Custom python is required, so that meson - # gets libs and config files properly - options["binaries"]["python"] = self.ctx.python_recipe.python_exe - options["binaries"]["python3"] = self.ctx.python_recipe.python_exe - options["properties"]["longdouble_format"] = "IEEE_DOUBLE_LE" if arch.arch in ["armeabi-v7a", "x86"] else "IEEE_QUAD_LE" + options["properties"]["longdouble_format"] = ( + "IEEE_DOUBLE_LE" if arch.arch in ["armeabi-v7a", "x86"] else "IEEE_QUAD_LE" + ) return options def get_recipe_env(self, arch, **kwargs): @@ -32,17 +31,16 @@ def get_recipe_env(self, arch, **kwargs): # NPY_DISABLE_SVML=1 allows numpy to build for non-AVX512 CPUs # See: https://github.com/numpy/numpy/issues/21196 env["NPY_DISABLE_SVML"] = "1" - env["TARGET_PYTHON_EXE"] = join(Recipe.get_recipe( - "python3", self.ctx).get_build_dir(arch.arch), "android-build", "python") + env["TARGET_PYTHON_EXE"] = join( + Recipe.get_recipe("python3", self.ctx).get_build_dir(arch.arch), + "android-build", + "python", + ) return env - def build_arch(self, arch): - super().build_arch(arch) - self.restore_hostpython_prerequisites(["cython"]) - def get_hostrecipe_env(self, arch=None): env = super().get_hostrecipe_env(arch=arch) - env['RANLIB'] = shutil.which('ranlib') + env["RANLIB"] = shutil.which("ranlib") return env From 0f26b7576df9d29c830f472acdeb8c1a963d68f1 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Sun, 19 Apr 2026 15:53:51 +0530 Subject: [PATCH 04/11] no shebang patching required now --- pythonforandroid/recipe.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 74eabbd086..84bbfb4fbc 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -977,11 +977,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): # Set the LANG, this isn't usually important but is a better default # as it occasionally matters how Python e.g. reads files env['LANG'] = "en_GB.UTF-8" - - # Binaries made by packages installed by pip - self.patch_shebangs(self._host_recipe.local_bin, self._host_recipe.python_exe) env["PATH"] = self._host_recipe.local_bin + ":" + self._host_recipe.site_bin + ":" + env["PATH"] - host_env = self.get_hostrecipe_env(arch) env['PYTHONPATH'] = host_env["PYTHONPATH"] @@ -1382,7 +1378,6 @@ def build_arch(self, arch): self.install_hostpython_prerequisites( packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites ) - self.patch_shebangs(self._host_recipe.local_bin, self.real_hostpython_location) env = self.get_recipe_env(arch, with_flags_in_cc=True) # make build dir separately From 170556c0b39a21dddcd0d7cfb0c2e81ac7491d5a Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Sun, 19 Apr 2026 15:55:13 +0530 Subject: [PATCH 05/11] fix kivy master build --- pythonforandroid/recipes/kivy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index 5f59457a7d..2446c2101b 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -25,7 +25,7 @@ def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None): def is_kivy_less_than_3(recipe=None, arch=None): return packaging.version.parse( str(get_kivy_version(recipe, arch)) - ) < packaging.version.Version("3.0.0") + ) < packaging.version.Version("3.0.0.dev0") class KivyRecipe(PyProjectRecipe): From 260302e35e6c01cc05441ba3fd826c0078518f76 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 11:08:11 +0530 Subject: [PATCH 06/11] fix numpy build --- pythonforandroid/meson_python.py | 76 +++++++++++++++++++ pythonforandroid/recipe.py | 60 ++++++++++++++- .../recipes/matplotlib/__init__.py | 1 + pythonforandroid/recipes/python3/__init__.py | 19 +++-- pythonforandroid/recipes/scipy/__init__.py | 1 - 5 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 pythonforandroid/meson_python.py diff --git a/pythonforandroid/meson_python.py b/pythonforandroid/meson_python.py new file mode 100644 index 0000000000..cd3ed9d508 --- /dev/null +++ b/pythonforandroid/meson_python.py @@ -0,0 +1,76 @@ +import sys +import json +import subprocess +import importlib.util +from os.path import join +from glob import glob + + +class C: + TARGET_PYTHON_PREFIX = globals().get("TARGET_PYTHON_PREFIX") + PYTHON_MAJOR_VERSION = globals().get("PYTHON_MAJOR_VERSION", sys.version_info.major) + PYTHON_MINOR_VERSION = globals().get("PYTHON_MINOR_VERSION", sys.version_info.minor) + PLATFORM_TAG = globals().get("PLATFORM_TAG", sys.platform) + PYTHON_SUFFIX = globals().get("PYTHON_SUFFIX", "") + + +def handle_python_info(): + + prefix = C.TARGET_PYTHON_PREFIX + + sysconfig_file = glob(join(prefix, "lib/python3.14/_sysconfigdata*.py"))[0] + + spec = importlib.util.spec_from_file_location("_android_cfg", sysconfig_file) + cfg = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cfg) + + ver_python = f"python{C.PYTHON_MAJOR_VERSION}.{C.PYTHON_MINOR_VERSION}" + + site_path = join(prefix, f"lib/{ver_python}/site-packages") + + android_paths = { + "stdlib": join(prefix, "lib"), + "platstdlib": join(prefix, "lib"), + "purelib": site_path, + "platlib": site_path, + "include": join(prefix, f"include/{ver_python}/"), + "platinclude": join(prefix, f"include/{ver_python}/"), + "scripts": join(prefix, "bin"), + "data": prefix, + } + + print( + json.dumps( + { + "variables": cfg.build_time_vars, + "paths": android_paths, + "sysconfig_paths": android_paths, + "install_paths": android_paths, + "version": f"{C.PYTHON_MAJOR_VERSION}.{C.PYTHON_MINOR_VERSION}", + "platform": C.PLATFORM_TAG, + "is_pypy": False, + "is_venv": False, + "link_libpython": True, + "suffix": C.PYTHON_SUFFIX, + "limited_api_suffix": ".abi3.so", + "is_freethreaded": False, + } + ) + ) + + +WHITELIST = { + "python_info.py": handle_python_info, +} + + +args = sys.argv[1:] + +if args: + cmd = args[0] + name = cmd.split("/")[-1] + + if name in WHITELIST: + sys.exit(WHITELIST[name]()) + +sys.exit(subprocess.run([sys.executable] + args).returncode) diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 84bbfb4fbc..bce8516e5b 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -10,8 +10,9 @@ import zipfile import urllib.request from urllib.request import urlretrieve -from os import listdir, unlink, environ, curdir, walk +from os import listdir, unlink, environ, curdir, walk, chmod from sys import stdout +from packaging.version import Version from multiprocessing import cpu_count import time try: @@ -1058,6 +1059,7 @@ def install_hostpython_prerequisites(self, packages=None, force_upgrade=True): pip_options = [ "install", *packages, + "-q", ] if force_upgrade: pip_options.append("--upgrade") @@ -1303,6 +1305,8 @@ def get_recipe_env(self, arch, **kwargs): file.close() env["DIST_EXTRA_CONFIG"] = build_opts + python_recipe = Recipe.get_recipe("python3", self.ctx) + env["INCLUDEPY"] = python_recipe.include_root(arch.arch) return env @staticmethod @@ -1374,7 +1378,6 @@ def build_arch(self, arch): if not (isfile(join(build_dir, "pyproject.toml")) or isfile(join(build_dir, "setup.py"))): warning("Skipping build because it does not appear to be a Python project.") return - self.install_hostpython_prerequisites( packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites ) @@ -1405,7 +1408,6 @@ class MesonRecipe(PyProjectRecipe): '''Recipe for projects which uses meson as build system''' meson_version = "1.4.0" - ninja_version = "1.11.1.1" skip_python = False '''If true, skips all Python build and installation steps. @@ -1414,10 +1416,55 @@ class MesonRecipe(PyProjectRecipe): def sanitize_flags(self, *flag_strings): return " ".join(flag_strings).strip().split(" ") + def get_python_wrapper(self, arch): + """ + Meson Python introspection runs on the host interpreter, but the + target Python (Android) cannot be executed on the build machine. + + We therefore run host Python and override sysconfig data to emulate + the target Android Python environment during Meson introspection. + """ + python_recipe = Recipe.get_recipe('python3', self.ctx) + target_prefix = python_recipe.get_python_root(arch) + python_file = join(self.ctx.root_dir, 'meson_python.py') + _arch = { + "arm64-v8a": ["aarch64"], + "x86_64": ["x86_64"], + "armeabi-v7a": ["arm"], + "x86": ["i686"], + }[arch.arch][0] + + # Real values pulled from android + # PYTHON_MAJOR_VERSION -> 3 + # PYTHON_MINOR_VERSION -> 14 + # PLATFORM_TAG eg -> 'android-24-arm64_v8a' + # PYTHON_SUFFIX eg -> '.cpython-314-aarch64-linux-android.so' + + _p_version = Version(python_recipe.version) + file_data = f"#!{self.real_hostpython_location}" + file_data += f"\nTARGET_PYTHON_PREFIX='{target_prefix}'" + file_data += f"\nPYTHON_MAJOR_VERSION='{_p_version.major}'" + file_data += f"\nPYTHON_MINOR_VERSION='{_p_version.minor}'" + file_data += f"\nPLATFORM_TAG='{self.get_wheel_platform_tags(arch.arch, self.ctx)[0]}'" + file_data += f"\nPYTHON_SUFFIX='.cpython-{_p_version.major}{_p_version.minor}-{_arch}-linux-android.so'" + + with open(python_file, "r") as f: + file_data += "\n" + f.read() + + wrapper_dir = join(self.get_build_dir(arch.arch), "p4a_python_wrapper") + ensure_dir(wrapper_dir) + wrapper_path = join(wrapper_dir, "python") + with open(wrapper_path, "w") as f: + f.write(file_data) + chmod(wrapper_path, 0o755) + + return wrapper_path + def get_recipe_meson_options(self, arch): env = self.get_recipe_env(arch, with_flags_in_cc=True) return { "binaries": { + "python": self.get_python_wrapper(arch), "c": arch.get_clang_exe(with_target=True), "cpp": arch.get_clang_exe(with_target=True, plus_plus=True), "ar": self.ctx.ndk.llvm_ar, @@ -1488,13 +1535,18 @@ def build_arch(self, arch): self.ensure_args('-Csetup-args=--cross-file', '-Csetup-args={}'.format(cross_file)) # ensure ninja and meson for dep in [ - "ninja=={}".format(self.ninja_version), + "ninja", "meson=={}".format(self.meson_version), ]: if dep not in self.hostpython_prerequisites: self.hostpython_prerequisites.append(dep) + if not self.skip_python: super().build_arch(arch) + else: + self.install_hostpython_prerequisites( + packages=["build[virtualenv]", "pip", "setuptools", "patchelf"] + self.hostpython_prerequisites + ) class RustCompiledComponentsRecipe(PyProjectRecipe): diff --git a/pythonforandroid/recipes/matplotlib/__init__.py b/pythonforandroid/recipes/matplotlib/__init__.py index 2213499efe..92fe711b6d 100644 --- a/pythonforandroid/recipes/matplotlib/__init__.py +++ b/pythonforandroid/recipes/matplotlib/__init__.py @@ -12,6 +12,7 @@ class MatplotlibRecipe(MesonRecipe): "pyparsing", "python-dateutil", ] + hostpython_prerequisites = ["meson-python>=0.13.1,<0.17.0", "pybind11>=2.13.2,!=2.13.3", "setuptools_scm>=7"] need_stl_shared = True def get_recipe_env(self, arch, **kwargs): diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index a5b60a2495..06821aab8f 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -82,7 +82,6 @@ class Python3Recipe(TargetPythonRecipe): # Android prefix '--prefix={prefix}', - '--exec-prefix={exec_prefix}', '--enable-loadable-sqlite-extensions', # Special cross compile args @@ -211,11 +210,17 @@ def apply_patches(self, arch, build_dir=None): super().apply_patches(arch, build_dir) def include_root(self, arch_name): - return join(self.get_build_dir(arch_name), 'Include') + return glob.glob(join(self.get_build_dir(arch_name), 'android-build', 'android-root', 'include/*/'))[0] def link_root(self, arch_name): return join(self.get_build_dir(arch_name), 'android-build') + def get_python_root(self, arch): + return join(self.get_build_dir(arch.arch), 'android-build', 'android-root') + + def get_android_python_exe(self, arch): + return join(self.get_python_root(arch), 'bin', self.name) + def should_build(self, arch): return not isfile(join(self.link_root(arch.arch), self._libpython)) @@ -342,9 +347,8 @@ def build_arch(self, arch): build_dir = join(recipe_build_dir, 'android-build') ensure_dir(build_dir) - # TODO: Get these dynamically, like bpo-30386 does - sys_prefix = '/usr/local' - sys_exec_prefix = '/usr/local' + sys_prefix = join(build_dir, "android-root") + ensure_dir(sys_prefix) env = self.get_recipe_env(arch) env = self.set_libs_flags(env, arch) @@ -363,8 +367,7 @@ def build_arch(self, arch): python_host_bin=self.get_recipe( 'host' + self.name, self.ctx ).python_exe, - prefix=sys_prefix, - exec_prefix=sys_exec_prefix)).split(' '), + prefix=sys_prefix).split(' ')), _env=env) shprint( @@ -373,6 +376,8 @@ def build_arch(self, arch): 'INSTSONAME={lib_name}'.format(lib_name=self._libpython), _env=env ) + shprint(sh.make, 'install', _env=env) + # rename executable if isfile("python"): sh.cp('python', 'libpythonbin.so') diff --git a/pythonforandroid/recipes/scipy/__init__.py b/pythonforandroid/recipes/scipy/__init__.py index 8cf46dab11..4406b354a7 100644 --- a/pythonforandroid/recipes/scipy/__init__.py +++ b/pythonforandroid/recipes/scipy/__init__.py @@ -17,7 +17,6 @@ class ScipyRecipe(MesonRecipe): def get_recipe_meson_options(self, arch): options = super().get_recipe_meson_options(arch) - options["binaries"]["python"] = self.ctx.python_recipe.python_exe options["binaries"]["fortran"] = self.place_wrapper(arch) options["properties"]["numpy-include-dir"] = join( self.ctx.get_python_install_dir(arch.arch), "numpy/_core/include" From 9af16a726768c2938c9e264d22b87b915d36c027 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 13:25:33 +0530 Subject: [PATCH 07/11] more wrappers for meson recipe --- pythonforandroid/recipe.py | 59 ++++++++++++++++--- .../recipes/matplotlib/__init__.py | 1 + pythonforandroid/recipes/numpy/__init__.py | 5 ++ pythonforandroid/recipes/pandas/__init__.py | 5 -- pythonforandroid/recipes/python3/__init__.py | 6 +- tests/recipes/test_hostpython3.py | 2 +- tests/recipes/test_python3.py | 18 ++---- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index bce8516e5b..55db2bc194 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -5,6 +5,7 @@ from re import match import sh +import subprocess import shutil import fnmatch import zipfile @@ -1408,6 +1409,7 @@ class MesonRecipe(PyProjectRecipe): '''Recipe for projects which uses meson as build system''' meson_version = "1.4.0" + pybind_version = "3.3.0" skip_python = False '''If true, skips all Python build and installation steps. @@ -1416,6 +1418,18 @@ class MesonRecipe(PyProjectRecipe): def sanitize_flags(self, *flag_strings): return " ".join(flag_strings).strip().split(" ") + def get_wrapper_dir(self, arch): + return join(self.get_build_dir(arch.arch), "p4a_wrappers") + + def write_wrapper(self, arch, name, content): + wrapper_dir = self.get_wrapper_dir(arch) + ensure_dir(wrapper_dir) + wrapper_path = join(wrapper_dir, name) + with open(wrapper_path, "w") as f: + f.write(content) + chmod(wrapper_path, 0o755) + return wrapper_path + def get_python_wrapper(self, arch): """ Meson Python introspection runs on the host interpreter, but the @@ -1451,19 +1465,50 @@ def get_python_wrapper(self, arch): with open(python_file, "r") as f: file_data += "\n" + f.read() - wrapper_dir = join(self.get_build_dir(arch.arch), "p4a_python_wrapper") - ensure_dir(wrapper_dir) - wrapper_path = join(wrapper_dir, "python") - with open(wrapper_path, "w") as f: - f.write(file_data) - chmod(wrapper_path, 0o755) + return self.write_wrapper(arch, "python", file_data) - return wrapper_path + def get_config_wrappers(self, arch, w_type: str): + wrapper_name = "" + version = "" + include_path = "" + + if w_type == "pybind11": + wrapper_name = "pybind11-config" + include_path = join(self._host_recipe.site_dir, "pybind11/include") + + version = None + try: + command = [self._host_recipe.real_hostpython_location, "-c", "import pybind11; print(pybind11.__version__)"] + version = subprocess.check_output(command).decode('utf-8').strip() + except Exception: + warning("Unable to get pybind11 version") + if version is None: + version = self.pybind_version + + elif w_type == "numpy": + wrapper_name = "numpy-config" + recipe = Recipe.get_recipe("numpy", self.ctx) + include_path = recipe.get_include(arch) + version = recipe.version + else: + raise ValueError(f"Unknown wrapper type: {w_type}") + + content = ( + f"#!/bin/sh\n" + f"if [ \"$1\" = \"--version\" ]; then\n" + f" echo '{version}'\n" + f"else\n" + f" echo '-I{include_path}'\n" + f"fi\n" + ) + return self.write_wrapper(arch, wrapper_name, content) def get_recipe_meson_options(self, arch): env = self.get_recipe_env(arch, with_flags_in_cc=True) return { "binaries": { + "pybind11-config": self.get_config_wrappers(arch, "pybind11"), + "numpy-config": self.get_config_wrappers(arch, "numpy"), "python": self.get_python_wrapper(arch), "c": arch.get_clang_exe(with_target=True), "cpp": arch.get_clang_exe(with_target=True, plus_plus=True), diff --git a/pythonforandroid/recipes/matplotlib/__init__.py b/pythonforandroid/recipes/matplotlib/__init__.py index 92fe711b6d..0257dbec31 100644 --- a/pythonforandroid/recipes/matplotlib/__init__.py +++ b/pythonforandroid/recipes/matplotlib/__init__.py @@ -12,6 +12,7 @@ class MatplotlibRecipe(MesonRecipe): "pyparsing", "python-dateutil", ] + pybind_version = "2.13.4" hostpython_prerequisites = ["meson-python>=0.13.1,<0.17.0", "pybind11>=2.13.2,!=2.13.3", "setuptools_scm>=7"] need_stl_shared = True diff --git a/pythonforandroid/recipes/numpy/__init__.py b/pythonforandroid/recipes/numpy/__init__.py index 4da77b769c..df1d294438 100644 --- a/pythonforandroid/recipes/numpy/__init__.py +++ b/pythonforandroid/recipes/numpy/__init__.py @@ -14,6 +14,11 @@ class NumpyRecipe(MesonRecipe): need_stl_shared = True min_ndk_api_support = 24 + def get_include(self, arch): + return join( + self.ctx.get_python_install_dir(arch.arch), "numpy/_core/include", + ) + def get_recipe_meson_options(self, arch): options = super().get_recipe_meson_options(arch) options["properties"]["longdouble_format"] = ( diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py index 6986e9efbe..a34147048e 100644 --- a/pythonforandroid/recipes/pandas/__init__.py +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -6,7 +6,6 @@ class PandasRecipe(MesonRecipe): version = 'v2.3.0' url = 'git+https://github.com/pandas-dev/pandas' depends = ['numpy', 'libbz2', 'liblzma'] - hostpython_prerequisites = ["Cython<4.0.0a0", "versioneer", "numpy"] # meson does not detects venv's cython patches = ['fix_numpy_includes.patch'] python_depends = ['python-dateutil', 'pytz'] need_stl_shared = True @@ -29,9 +28,5 @@ def get_recipe_env(self, arch, **kwargs): env['LDFLAGS'] += f' -landroid -l{self.stl_lib_name}' return env - def build_arch(self, arch): - super().build_arch(arch) - self.restore_hostpython_prerequisites(["cython"]) - recipe = PandasRecipe() diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index 06821aab8f..f3d52ef4c3 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -210,7 +210,11 @@ def apply_patches(self, arch, build_dir=None): super().apply_patches(arch, build_dir) def include_root(self, arch_name): - return glob.glob(join(self.get_build_dir(arch_name), 'android-build', 'android-root', 'include/*/'))[0] + _p_version = Version(self.version) + return join( + self.get_build_dir(arch_name), 'android-build', 'android-root', + 'include', f'python{_p_version.major}.{_p_version.minor}' + ) def link_root(self, arch_name): return join(self.get_build_dir(arch_name), 'android-build') diff --git a/tests/recipes/test_hostpython3.py b/tests/recipes/test_hostpython3.py index ba05f3c144..111cda0b85 100644 --- a/tests/recipes/test_hostpython3.py +++ b/tests/recipes/test_hostpython3.py @@ -27,7 +27,7 @@ def test_property__exe_name_no_version(self): self.recipe._version = hostpython_version def test_property__exe_name(self): - self.assertEqual(self.recipe._exe_name, 'python3') + self.assertEqual(self.recipe._exe_name, 'python') @mock.patch("pythonforandroid.recipes.hostpython3.Path.exists") def test_should_build(self, mock_exists): diff --git a/tests/recipes/test_python3.py b/tests/recipes/test_python3.py index 57b7ef8e01..e6f87d7f8b 100644 --- a/tests/recipes/test_python3.py +++ b/tests/recipes/test_python3.py @@ -26,14 +26,6 @@ def test_property__libpython(self): f'libpython{self.recipe.link_version}.so' ) - def test_include_root(self): - expected_include_dir = join( - self.recipe.get_build_dir(self.arch.arch), 'Include', - ) - self.assertEqual( - expected_include_dir, self.recipe.include_root(self.arch.arch) - ) - def test_link_root(self): expected_link_root = join( self.recipe.get_build_dir(self.arch.arch), 'android-build', @@ -116,11 +108,11 @@ def test_build_arch( mock_sh_command.mock_calls, ) mock_open_zlib.assert_called() - self.assertEqual(mock_make.call_count, 1) - for make_call, kw in mock_make.call_args_list: - self.assertIn( - f'INSTSONAME={self.recipe._libpython}', make_call - ) + self.assertEqual(mock_make.call_count, 2) + make_call, kw = mock_make.call_args_list[0] + self.assertIn( + f'INSTSONAME={self.recipe._libpython}', make_call + ) mock_cp.assert_called_with( "pyconfig.h", join(recipe_build_dir, 'Include'), ) From 4bbf961d2bc71dc0ff5caf85370d5e75a30914a2 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 13:26:54 +0530 Subject: [PATCH 08/11] flake8 fix --- tests/recipes/test_python3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/recipes/test_python3.py b/tests/recipes/test_python3.py index e6f87d7f8b..e3b69e4bcb 100644 --- a/tests/recipes/test_python3.py +++ b/tests/recipes/test_python3.py @@ -109,7 +109,7 @@ def test_build_arch( ) mock_open_zlib.assert_called() self.assertEqual(mock_make.call_count, 2) - make_call, kw = mock_make.call_args_list[0] + make_call, kw = mock_make.call_args_list[0] self.assertIn( f'INSTSONAME={self.recipe._libpython}', make_call ) From e9b36aa60d9b15a3dfa09a9b28bfffac4b38ed81 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 13:32:11 +0530 Subject: [PATCH 09/11] add back pandas host preq --- pythonforandroid/recipes/pandas/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py index a34147048e..83b7a42d08 100644 --- a/pythonforandroid/recipes/pandas/__init__.py +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -6,6 +6,7 @@ class PandasRecipe(MesonRecipe): version = 'v2.3.0' url = 'git+https://github.com/pandas-dev/pandas' depends = ['numpy', 'libbz2', 'liblzma'] + hostpython_prerequisites = ["versioneer[toml]", "numpy>=2.0"] # meson does not detects venv's cython patches = ['fix_numpy_includes.patch'] python_depends = ['python-dateutil', 'pytz'] need_stl_shared = True From af0365746872a6d148d805cfcbff876d421f9300 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 13:57:04 +0530 Subject: [PATCH 10/11] fix bug in logger --- pythonforandroid/logger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pythonforandroid/logger.py b/pythonforandroid/logger.py index 9f09a20947..af252a2697 100644 --- a/pythonforandroid/logger.py +++ b/pythonforandroid/logger.py @@ -141,7 +141,6 @@ def shprint(command, *args, **kwargs): kwargs.pop("silent", False) if silent: kwargs["_out"] = None - kwargs["_err"] = None is_critical = kwargs.pop('_critical', False) tail_n = kwargs.pop('_tail', None) From 2adb6144b9745be2d957f5cff686a4ebc7d171af Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Tue, 21 Apr 2026 14:48:07 +0530 Subject: [PATCH 11/11] add back cython for pandas --- pythonforandroid/recipes/pandas/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py index 83b7a42d08..de937fd1a3 100644 --- a/pythonforandroid/recipes/pandas/__init__.py +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -6,7 +6,7 @@ class PandasRecipe(MesonRecipe): version = 'v2.3.0' url = 'git+https://github.com/pandas-dev/pandas' depends = ['numpy', 'libbz2', 'liblzma'] - hostpython_prerequisites = ["versioneer[toml]", "numpy>=2.0"] # meson does not detects venv's cython + hostpython_prerequisites = ["versioneer[toml]", "numpy>=2.0", "Cython<4.0.0a0"] # meson does not detects venv's cython patches = ['fix_numpy_includes.patch'] python_depends = ['python-dateutil', 'pytz'] need_stl_shared = True @@ -29,5 +29,9 @@ def get_recipe_env(self, arch, **kwargs): env['LDFLAGS'] += f' -landroid -l{self.stl_lib_name}' return env + def build_arch(self, arch): + super().build_arch(arch) + self.restore_hostpython_prerequisites(["cython"]) + recipe = PandasRecipe()