Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly search native lib dir in ctypes, fixes #1770 #1772

Merged
merged 1 commit into from Apr 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

import os


def get_activity_lib_dir(activity_name):
from jnius import autoclass

# Get the actual activity instance:
activity_class = autoclass(activity_name)
if activity_class is None:
return None
activity = None
if hasattr(activity_class, "mActivity") and \
activity_class.mActivity is not None:
activity = activity_class.mActivity
elif hasattr(activity_class, "mService") and \
activity_class.mService is not None:
activity = activity_class.mService
if activity is None:
return None

# Extract the native lib dir from the activity instance:
package_name = activity.getApplicationContext().getPackageName()
manager = activity.getApplicationContext().getPackageManager()
manager_class = autoclass("android.content.pm.PackageManager")
native_lib_dir = manager.getApplicationInfo(
package_name, manager_class.GET_SHARED_LIBRARY_FILES
).nativeLibraryDir
return native_lib_dir


def does_libname_match_filename(search_name, file_path):
# Filter file names so given search_name="mymodule" we match one of:
# mymodule.so (direct name + .so)
# libmymodule.so (added lib prefix)
# mymodule.arm64.so (added dot-separated middle parts)
# mymodule.so.1.3.4 (added dot-separated version tail)
# and all above (all possible combinations)
import re
file_name = os.path.basename(file_path)
return (re.match(r"^(lib)?" + re.escape(search_name) +
r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)


def find_library(name):
# Obtain all places for native libraries:
lib_search_dirs = ["/system/lib"]
lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
if lib_dir_1 is not None:
lib_search_dirs.insert(0, lib_dir_1)
lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
lib_search_dirs.insert(0, lib_dir_2)

# Now scan the lib dirs:
for lib_dir in [l for l in lib_search_dirs if os.path.exists(l)]:
filelist = [
f for f in os.listdir(lib_dir)
if does_libname_match_filename(name, f)
]
if len(filelist) > 0:
return os.path.join(lib_dir, filelist[0])
return None
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -67,4 +67,19 @@
@@ -67,4 +67,11 @@
return fname
return None

+# This patch overrides the find_library to look in the right places on
+# Android
+if True:
+ from android._ctypes_library_finder import find_library as _find_lib
+ def find_library(name):
+ # Check the user app libs and system libraries directory:
+ app_root = os.path.normpath(os.path.abspath('../../'))
+ lib_search_dirs = [os.path.join(app_root, 'lib'), "/system/lib"]
+ for lib_dir in lib_search_dirs:
+ for filename in os.listdir(lib_dir):
+ if filename.endswith('.so') and (
+ filename.startswith("lib" + name + ".") or
+ filename.startswith(name + ".")):
+ return os.path.join(lib_dir, filename)
+ return None
+ return _find_lib(name)
+
elif os.name == "posix" and sys.platform == "darwin":
124 changes: 124 additions & 0 deletions tests/test_androidmodule_ctypes_finder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@

import mock
from mock import MagicMock
import os
import shutil
import sys
import tempfile


# Import the tested android._ctypes_library_finder module,
# making sure android._android won't crash us!
# (since android._android is android-only / not compilable on desktop)
android_module_folder = os.path.abspath(os.path.join(
os.path.dirname(__file__),
"..", "pythonforandroid", "recipes", "android", "src"
))
sys.path.insert(0, android_module_folder)
AndreMiras marked this conversation as resolved.
Show resolved Hide resolved
sys.modules['android._android'] = MagicMock()
import android._ctypes_library_finder
sys.path.remove(android_module_folder)


@mock.patch.dict('sys.modules', jnius=MagicMock())
def test_get_activity_lib_dir():
AndreMiras marked this conversation as resolved.
Show resolved Hide resolved
import jnius # should get us our fake module

# Short test that it works when activity doesn't exist:
jnius.autoclass = MagicMock()
jnius.autoclass.return_value = None
assert android._ctypes_library_finder.get_activity_lib_dir(
"JavaClass"
) is None
assert mock.call("JavaClass") in jnius.autoclass.call_args_list

# Comprehensive test that verifies getApplicationInfo() call:
activity = MagicMock()
app_context = activity.getApplicationContext()
app_context.getPackageName.return_value = "test.package"
app_info = app_context.getPackageManager().getApplicationInfo()
app_info.nativeLibraryDir = '/testpath'

def pick_class(name):
cls = MagicMock()
if name == "JavaClass":
cls.mActivity = activity
elif name == "android.content.pm.PackageManager":
# Manager class:
cls.GET_SHARED_LIBRARY_FILES = 1024
return cls

jnius.autoclass = MagicMock(side_effect=pick_class)
assert android._ctypes_library_finder.get_activity_lib_dir(
"JavaClass"
) == "/testpath"
assert mock.call("JavaClass") in jnius.autoclass.call_args_list
assert mock.call("test.package", 1024) in (
app_context.getPackageManager().getApplicationInfo.call_args_list
)


@mock.patch.dict('sys.modules', jnius=MagicMock())
def test_find_library():
AndreMiras marked this conversation as resolved.
Show resolved Hide resolved
test_d = tempfile.mkdtemp(prefix="p4a-android-ctypes-test-libdir-")
AndreMiras marked this conversation as resolved.
Show resolved Hide resolved
try:
with open(os.path.join(test_d, "mymadeuplib.so.5"), "w"):
pass
import jnius # should get us our fake module

# Test with mActivity returned:
jnius.autoclass = MagicMock()
jnius.autoclass().mService = None
app_context = jnius.autoclass().mActivity.getApplicationContext()
app_info = app_context.getPackageManager().getApplicationInfo()
app_info.nativeLibraryDir = '/doesnt-exist-testpath'
assert android._ctypes_library_finder.find_library(
"mymadeuplib"
) is None
assert mock.call("org.kivy.android.PythonActivity") in (
jnius.autoclass.call_args_list
)
app_info.nativeLibraryDir = test_d
assert os.path.normpath(android._ctypes_library_finder.find_library(
"mymadeuplib"
)) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))

# Test with mService returned:
jnius.autoclass = MagicMock()
jnius.autoclass().mActivity = None
app_context = jnius.autoclass().mService.getApplicationContext()
app_info = app_context.getPackageManager().getApplicationInfo()
app_info.nativeLibraryDir = '/doesnt-exist-testpath'
assert android._ctypes_library_finder.find_library(
"mymadeuplib"
) is None
app_info.nativeLibraryDir = test_d
assert os.path.normpath(android._ctypes_library_finder.find_library(
"mymadeuplib"
)) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))
finally:
shutil.rmtree(test_d)


def test_does_libname_match_filename():
assert android._ctypes_library_finder.does_libname_match_filename(
"mylib", "mylib.so"
)
assert not android._ctypes_library_finder.does_libname_match_filename(
"mylib", "amylib.so"
)
assert not android._ctypes_library_finder.does_libname_match_filename(
"mylib", "mylib.txt"
)
assert not android._ctypes_library_finder.does_libname_match_filename(
"mylib", "mylib"
)
assert android._ctypes_library_finder.does_libname_match_filename(
"mylib", "libmylib.test.so.1.2.3"
)
assert not android._ctypes_library_finder.does_libname_match_filename(
"mylib", "libtest.mylib.so"
)
assert android._ctypes_library_finder.does_libname_match_filename(
"mylib", "mylib.so.5"
)