diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py new file mode 100644 index 0000000000..7999d02402 --- /dev/null +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -0,0 +1,35 @@ +from os.path import join + +from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe + + +class PandasRecipe(CppCompiledComponentsPythonRecipe): + version = '1.0.3' + url = 'https://github.com/pandas-dev/pandas/releases/download/v{version}/pandas-{version}.tar.gz' # noqa + + depends = ['cython', 'numpy', 'pytz', 'libbz2', 'liblzma'] + conflicts = ['python2'] + + python_depends = ['python-dateutil'] + patches = ['fix_numpy_includes.patch'] + + call_hostpython_via_targetpython = False + + def get_recipe_env(self, arch): + env = super(PandasRecipe, self).get_recipe_env(arch) + # we need the includes from our installed numpy at site packages + # because we need some includes generated at numpy's compile time + env['NUMPY_INCLUDES'] = join( + self.ctx.get_python_install_dir(), "numpy/core/include", + ) + + # this flag below is to fix a runtime error: + # ImportError: dlopen failed: cannot locate symbol + # "_ZTVSt12length_error" referenced by + # "/data/data/org.test.matplotlib_testapp/files/app/_python_bundle + # /site-packages/pandas/_libs/window/aggregations.so"... + env['LDFLAGS'] += ' -landroid' + return env + + +recipe = PandasRecipe() diff --git a/pythonforandroid/recipes/pandas/fix_numpy_includes.patch b/pythonforandroid/recipes/pandas/fix_numpy_includes.patch new file mode 100644 index 0000000000..ef1643b9b1 --- /dev/null +++ b/pythonforandroid/recipes/pandas/fix_numpy_includes.patch @@ -0,0 +1,31 @@ +--- pandas-1.0.1/setup.py.orig 2020-02-05 17:15:24.000000000 +0100 ++++ pandas-1.0.1/setup.py 2020-03-15 13:47:57.612237225 +0100 +@@ -37,11 +37,12 @@ min_cython_ver = "0.29.13" # note: sync + + setuptools_kwargs = { + "install_requires": [ +- "python-dateutil >= 2.6.1", +- "pytz >= 2017.2", +- f"numpy >= {min_numpy_ver}", ++ # dependencies managed via p4a's recipe ++ # "python-dateutil >= 2.6.1", ++ # "pytz >= 2017.2", ++ # f"numpy >= {min_numpy_ver}", + ], +- "setup_requires": [f"numpy >= {min_numpy_ver}"], ++ "setup_requires": [], + "zip_safe": False, + } + +@@ -514,7 +515,10 @@ def maybe_cythonize(extensions, *args, * + ) + raise RuntimeError("Cannot cythonize without Cython installed.") + +- numpy_incl = pkg_resources.resource_filename("numpy", "core/include") ++ if 'NUMPY_INCLUDES' in os.environ: ++ numpy_incl = os.environ['NUMPY_INCLUDES'] ++ else: ++ numpy_incl = pkg_resources.resource_filename("numpy", "core/include") + # TODO: Is this really necessary here? + for ext in extensions: + if hasattr(ext, "include_dirs") and numpy_incl not in ext.include_dirs: diff --git a/tests/recipes/test_pandas.py b/tests/recipes/test_pandas.py new file mode 100644 index 0000000000..23cefb43c9 --- /dev/null +++ b/tests/recipes/test_pandas.py @@ -0,0 +1,51 @@ +import unittest + +from os.path import join +from unittest import mock + +from tests.recipes.recipe_lib_test import RecipeCtx + + +class TestPandasRecipe(RecipeCtx, unittest.TestCase): + """ + TestCase for recipe :mod:`~pythonforandroid.recipes.pandas` + """ + recipe_name = "pandas" + + @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") + @mock.patch("pythonforandroid.build.ensure_dir") + @mock.patch("pythonforandroid.archs.glob") + @mock.patch("pythonforandroid.archs.find_executable") + def test_get_recipe_env( + self, + mock_find_executable, + mock_glob, + mock_ensure_dir, + mock_check_recipe_choices, + ): + """ + Test that method + :meth:`~pythonforandroid.recipes.pandas.PandasRecipe.get_recipe_env` + returns the expected flags + """ + + mock_find_executable.return_value = ( + "/opt/android/android-ndk/toolchains/" + "llvm/prebuilt/linux-x86_64/bin/clang" + ) + mock_glob.return_value = ["llvm"] + mock_check_recipe_choices.return_value = sorted( + self.ctx.recipe_build_order + ) + numpy_includes = join( + self.ctx.get_python_install_dir(), "numpy/core/include", + ) + env = self.recipe.get_recipe_env(self.arch) + self.assertIn(numpy_includes, env["NUMPY_INCLUDES"]) + self.assertIn(" -landroid", env["LDFLAGS"]) + + # make sure that the mocked methods are actually called + mock_glob.assert_called() + mock_ensure_dir.assert_called() + mock_find_executable.assert_called() + mock_check_recipe_choices.assert_called()