Skip to content

Commit

Permalink
Merge pull request #1772 from JonasT/ctypes_patch_fix
Browse files Browse the repository at this point in the history
Properly search native lib dir in ctypes, fixes #1770
  • Loading branch information
AndreMiras authored Apr 6, 2019
2 parents 16d4d29 + 6eb223c commit 6b12281
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 11 deletions.
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)
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():
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():
test_d = tempfile.mkdtemp(prefix="p4a-android-ctypes-test-libdir-")
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"
)

0 comments on commit 6b12281

Please sign in to comment.