Skip to content

Commit 6b12281

Browse files
authored
Merge pull request kivy#1772 from JonasT/ctypes_patch_fix
Properly search native lib dir in ctypes, fixes kivy#1770
2 parents 16d4d29 + 6eb223c commit 6b12281

File tree

3 files changed

+190
-11
lines changed

3 files changed

+190
-11
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
import os
3+
4+
5+
def get_activity_lib_dir(activity_name):
6+
from jnius import autoclass
7+
8+
# Get the actual activity instance:
9+
activity_class = autoclass(activity_name)
10+
if activity_class is None:
11+
return None
12+
activity = None
13+
if hasattr(activity_class, "mActivity") and \
14+
activity_class.mActivity is not None:
15+
activity = activity_class.mActivity
16+
elif hasattr(activity_class, "mService") and \
17+
activity_class.mService is not None:
18+
activity = activity_class.mService
19+
if activity is None:
20+
return None
21+
22+
# Extract the native lib dir from the activity instance:
23+
package_name = activity.getApplicationContext().getPackageName()
24+
manager = activity.getApplicationContext().getPackageManager()
25+
manager_class = autoclass("android.content.pm.PackageManager")
26+
native_lib_dir = manager.getApplicationInfo(
27+
package_name, manager_class.GET_SHARED_LIBRARY_FILES
28+
).nativeLibraryDir
29+
return native_lib_dir
30+
31+
32+
def does_libname_match_filename(search_name, file_path):
33+
# Filter file names so given search_name="mymodule" we match one of:
34+
# mymodule.so (direct name + .so)
35+
# libmymodule.so (added lib prefix)
36+
# mymodule.arm64.so (added dot-separated middle parts)
37+
# mymodule.so.1.3.4 (added dot-separated version tail)
38+
# and all above (all possible combinations)
39+
import re
40+
file_name = os.path.basename(file_path)
41+
return (re.match(r"^(lib)?" + re.escape(search_name) +
42+
r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)
43+
44+
45+
def find_library(name):
46+
# Obtain all places for native libraries:
47+
lib_search_dirs = ["/system/lib"]
48+
lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
49+
if lib_dir_1 is not None:
50+
lib_search_dirs.insert(0, lib_dir_1)
51+
lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
52+
if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
53+
lib_search_dirs.insert(0, lib_dir_2)
54+
55+
# Now scan the lib dirs:
56+
for lib_dir in [l for l in lib_search_dirs if os.path.exists(l)]:
57+
filelist = [
58+
f for f in os.listdir(lib_dir)
59+
if does_libname_match_filename(name, f)
60+
]
61+
if len(filelist) > 0:
62+
return os.path.join(lib_dir, filelist[0])
63+
return None
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
22
--- a/Lib/ctypes/util.py
33
+++ b/Lib/ctypes/util.py
4-
@@ -67,4 +67,19 @@
4+
@@ -67,4 +67,11 @@
55
return fname
66
return None
77

88
+# This patch overrides the find_library to look in the right places on
99
+# Android
1010
+if True:
11+
+ from android._ctypes_library_finder import find_library as _find_lib
1112
+ def find_library(name):
12-
+ # Check the user app libs and system libraries directory:
13-
+ app_root = os.path.normpath(os.path.abspath('../../'))
14-
+ lib_search_dirs = [os.path.join(app_root, 'lib'), "/system/lib"]
15-
+ for lib_dir in lib_search_dirs:
16-
+ for filename in os.listdir(lib_dir):
17-
+ if filename.endswith('.so') and (
18-
+ filename.startswith("lib" + name + ".") or
19-
+ filename.startswith(name + ".")):
20-
+ return os.path.join(lib_dir, filename)
21-
+ return None
13+
+ return _find_lib(name)
2214
+
2315
elif os.name == "posix" and sys.platform == "darwin":
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
2+
import mock
3+
from mock import MagicMock
4+
import os
5+
import shutil
6+
import sys
7+
import tempfile
8+
9+
10+
# Import the tested android._ctypes_library_finder module,
11+
# making sure android._android won't crash us!
12+
# (since android._android is android-only / not compilable on desktop)
13+
android_module_folder = os.path.abspath(os.path.join(
14+
os.path.dirname(__file__),
15+
"..", "pythonforandroid", "recipes", "android", "src"
16+
))
17+
sys.path.insert(0, android_module_folder)
18+
sys.modules['android._android'] = MagicMock()
19+
import android._ctypes_library_finder
20+
sys.path.remove(android_module_folder)
21+
22+
23+
@mock.patch.dict('sys.modules', jnius=MagicMock())
24+
def test_get_activity_lib_dir():
25+
import jnius # should get us our fake module
26+
27+
# Short test that it works when activity doesn't exist:
28+
jnius.autoclass = MagicMock()
29+
jnius.autoclass.return_value = None
30+
assert android._ctypes_library_finder.get_activity_lib_dir(
31+
"JavaClass"
32+
) is None
33+
assert mock.call("JavaClass") in jnius.autoclass.call_args_list
34+
35+
# Comprehensive test that verifies getApplicationInfo() call:
36+
activity = MagicMock()
37+
app_context = activity.getApplicationContext()
38+
app_context.getPackageName.return_value = "test.package"
39+
app_info = app_context.getPackageManager().getApplicationInfo()
40+
app_info.nativeLibraryDir = '/testpath'
41+
42+
def pick_class(name):
43+
cls = MagicMock()
44+
if name == "JavaClass":
45+
cls.mActivity = activity
46+
elif name == "android.content.pm.PackageManager":
47+
# Manager class:
48+
cls.GET_SHARED_LIBRARY_FILES = 1024
49+
return cls
50+
51+
jnius.autoclass = MagicMock(side_effect=pick_class)
52+
assert android._ctypes_library_finder.get_activity_lib_dir(
53+
"JavaClass"
54+
) == "/testpath"
55+
assert mock.call("JavaClass") in jnius.autoclass.call_args_list
56+
assert mock.call("test.package", 1024) in (
57+
app_context.getPackageManager().getApplicationInfo.call_args_list
58+
)
59+
60+
61+
@mock.patch.dict('sys.modules', jnius=MagicMock())
62+
def test_find_library():
63+
test_d = tempfile.mkdtemp(prefix="p4a-android-ctypes-test-libdir-")
64+
try:
65+
with open(os.path.join(test_d, "mymadeuplib.so.5"), "w"):
66+
pass
67+
import jnius # should get us our fake module
68+
69+
# Test with mActivity returned:
70+
jnius.autoclass = MagicMock()
71+
jnius.autoclass().mService = None
72+
app_context = jnius.autoclass().mActivity.getApplicationContext()
73+
app_info = app_context.getPackageManager().getApplicationInfo()
74+
app_info.nativeLibraryDir = '/doesnt-exist-testpath'
75+
assert android._ctypes_library_finder.find_library(
76+
"mymadeuplib"
77+
) is None
78+
assert mock.call("org.kivy.android.PythonActivity") in (
79+
jnius.autoclass.call_args_list
80+
)
81+
app_info.nativeLibraryDir = test_d
82+
assert os.path.normpath(android._ctypes_library_finder.find_library(
83+
"mymadeuplib"
84+
)) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))
85+
86+
# Test with mService returned:
87+
jnius.autoclass = MagicMock()
88+
jnius.autoclass().mActivity = None
89+
app_context = jnius.autoclass().mService.getApplicationContext()
90+
app_info = app_context.getPackageManager().getApplicationInfo()
91+
app_info.nativeLibraryDir = '/doesnt-exist-testpath'
92+
assert android._ctypes_library_finder.find_library(
93+
"mymadeuplib"
94+
) is None
95+
app_info.nativeLibraryDir = test_d
96+
assert os.path.normpath(android._ctypes_library_finder.find_library(
97+
"mymadeuplib"
98+
)) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))
99+
finally:
100+
shutil.rmtree(test_d)
101+
102+
103+
def test_does_libname_match_filename():
104+
assert android._ctypes_library_finder.does_libname_match_filename(
105+
"mylib", "mylib.so"
106+
)
107+
assert not android._ctypes_library_finder.does_libname_match_filename(
108+
"mylib", "amylib.so"
109+
)
110+
assert not android._ctypes_library_finder.does_libname_match_filename(
111+
"mylib", "mylib.txt"
112+
)
113+
assert not android._ctypes_library_finder.does_libname_match_filename(
114+
"mylib", "mylib"
115+
)
116+
assert android._ctypes_library_finder.does_libname_match_filename(
117+
"mylib", "libmylib.test.so.1.2.3"
118+
)
119+
assert not android._ctypes_library_finder.does_libname_match_filename(
120+
"mylib", "libtest.mylib.so"
121+
)
122+
assert android._ctypes_library_finder.does_libname_match_filename(
123+
"mylib", "mylib.so.5"
124+
)

0 commit comments

Comments
 (0)