From e809c9eb92979cb6cb2a5dde5adcdcf9d2c32f0a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Nov 2025 21:58:08 +0100 Subject: [PATCH] android: Complete support for android:extractNativeLibs=false This is the non-legacy way to ship native libraries nowadays, leaving them in the .apk so that there doesn't need to be an extraction step and the app takes up less space. --- direct/src/dist/FreezeTool.py | 2 +- direct/src/dist/commands.py | 12 +++---- direct/src/dist/installers.py | 4 +-- dtool/src/dtoolutil/load_dso.cxx | 10 ++++++ makepanda/makepanda.py | 4 +-- panda/src/android/PandaActivity.java | 2 +- panda/src/android/config_android.cxx | 26 ++++++++++++++ panda/src/android/config_android.h | 1 + pandatool/src/deploy-stub/android_main.cxx | 13 +++---- .../{android_log.c => android_support.cxx} | 34 +++++++++++++++---- 10 files changed, 82 insertions(+), 26 deletions(-) rename pandatool/src/deploy-stub/{android_log.c => android_support.cxx} (50%) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index 0d6e4a0ee1..34f9a1d09f 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -114,7 +114,7 @@ ignoreImports = { 'toml.encoder': ['numpy'], 'py._builtin': ['__builtin__'], - 'site': ['android_log'], + 'site': ['android_support'], } if sys.version_info >= (3, 8): diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 241bc6391b..d0b4af8814 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -192,14 +192,14 @@ SITE_PY_ANDROID = """ # module. import sys, os from importlib import _bootstrap, _bootstrap_external +from android_support import log_write as android_log_write +from android_support import find_library class AndroidExtensionFinder: @classmethod def find_spec(cls, fullname, path=None, target=None): - soname = 'libpy.' + fullname + '.so' - path = os.path.join(sys.platlibdir, soname) - - if os.path.exists(path): + path = find_library('py.' + fullname) + if path: loader = _bootstrap_external.ExtensionFileLoader(fullname, path) return _bootstrap.ModuleSpec(fullname, loader, origin=path) @@ -210,8 +210,6 @@ sys.meta_path.append(AndroidExtensionFinder) from _frozen_importlib import _imp, FrozenImporter from io import RawIOBase, TextIOWrapper -from android_log import write as android_log_write - sys.frozen = True @@ -869,7 +867,7 @@ class build_apps(setuptools.Command): if category: application.set('android:appCategory', category) application.set('android:debuggable', ('false', 'true')[self.android_debuggable]) - application.set('android:extractNativeLibs', 'true') + application.set('android:extractNativeLibs', 'false') application.set('android:hardwareAccelerated', 'true') app_icon = self.icon_objects.get('*', self.icon_objects.get(self.macos_main_app)) diff --git a/direct/src/dist/installers.py b/direct/src/dist/installers.py index 85fc3a7335..c5b1475190 100644 --- a/direct/src/dist/installers.py +++ b/direct/src/dist/installers.py @@ -232,10 +232,10 @@ def create_aab(command, basename, build_dir): config.optimizations.splits_config.Clear() if axml.extract_native_libs: config.optimizations.uncompress_native_libraries.enabled = False - config.optimizations.uncompress_native_libraries.alignment = \ - UncompressNativeLibraries.PageAlignment.PAGE_ALIGNMENT_16K else: config.optimizations.uncompress_native_libraries.enabled = True + config.optimizations.uncompress_native_libraries.alignment = \ + UncompressNativeLibraries.PageAlignment.PAGE_ALIGNMENT_16K config.compression.uncompressed_glob.append('res/raw/**') bundle.add_subfile('BundleConfig.pb', p3d.StringStream(config.SerializeToString()), 9) diff --git a/dtool/src/dtoolutil/load_dso.cxx b/dtool/src/dtoolutil/load_dso.cxx index 95650b88c2..ea212009a7 100644 --- a/dtool/src/dtoolutil/load_dso.cxx +++ b/dtool/src/dtoolutil/load_dso.cxx @@ -19,11 +19,15 @@ using std::string; static Filename resolve_dso(const DSearchPath &path, const Filename &filename) { if (filename.is_local()) { if ((path.get_num_directories()==1)&&(path.get_directory(0)=="")) { +#ifdef ANDROID + return filename; +#else // This is a special case, meaning to search in the same directory in // which libp3dtool.dll, or the exe, was started from. Filename dtoolpath = ExecutionEnvironment::get_dtool_name(); DSearchPath spath(dtoolpath.get_dirname()); return spath.find_file(filename); +#endif } else { return path.find_file(filename); } @@ -119,7 +123,13 @@ get_dso_symbol(void *handle, const string &name) { void * load_dso(const DSearchPath &path, const Filename &filename) { Filename abspath = resolve_dso(path, filename); +#ifdef ANDROID + // We just try to load it on Android, because we can't verify right now + // whether it might just be an unextracted library. + if (abspath.empty()) { +#else if (!abspath.is_regular_file()) { +#endif // Make sure the error flag is cleared, to prevent a subsequent call to // load_dso_error() from returning a previously stored error. dlerror(); diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 9c1a14f081..ad0b72777c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -5967,10 +5967,10 @@ if PkgSkip("PYTHON") == 0: TargetAdd('classes.dex', input='org/jnius/NativeInvocationHandler.class') PyTargetAdd('deploy-stubw_android_main.obj', opts=OPTS, input='android_main.cxx') - PyTargetAdd('deploy-stubw_android_log.obj', opts=OPTS, input='android_log.c') + PyTargetAdd('deploy-stubw_android_support.obj', opts=OPTS, input='android_support.cxx') PyTargetAdd('libdeploy-stubw.dll', input='android_native_app_glue.obj') PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_main.obj') - PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_log.obj') + PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_support.obj') PyTargetAdd('libdeploy-stubw.dll', input=COMMON_PANDA_LIBS) PyTargetAdd('libdeploy-stubw.dll', input='libp3android.dll') PyTargetAdd('libdeploy-stubw.dll', opts=['DEPLOYSTUB', 'ANDROID']) diff --git a/panda/src/android/PandaActivity.java b/panda/src/android/PandaActivity.java index 560375c90a..866c5bfc34 100644 --- a/panda/src/android/PandaActivity.java +++ b/panda/src/android/PandaActivity.java @@ -132,7 +132,7 @@ public class PandaActivity extends NativeActivity { /** * Returns the path to some other native library. */ - public String findLibraryPath(String libname) { + public String findLibrary(String libname) { BaseDexClassLoader classLoader = (BaseDexClassLoader)getClassLoader(); return classLoader.findLibrary(libname); } diff --git a/panda/src/android/config_android.cxx b/panda/src/android/config_android.cxx index 760cf8f51c..88ddba3c50 100644 --- a/panda/src/android/config_android.cxx +++ b/panda/src/android/config_android.cxx @@ -27,6 +27,7 @@ jmethodID jni_PandaActivity_readBitmap; jmethodID jni_PandaActivity_createBitmap; jmethodID jni_PandaActivity_compressBitmap; jmethodID jni_PandaActivity_showToast; +jmethodID jni_PandaActivity_findLibrary; jclass jni_Activity; jmethodID jni_Activity_setTitle; @@ -83,6 +84,9 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) { jni_PandaActivity_showToast = env->GetMethodID(jni_PandaActivity, "showToast", "(Ljava/lang/String;I)V"); + jni_PandaActivity_findLibrary = env->GetMethodID(jni_PandaActivity, + "findLibrary", "(Ljava/lang/String;)Ljava/lang/String;"); + jni_Activity = env->FindClass("android/app/Activity"); jni_Activity = (jclass) env->NewGlobalRef(jni_Activity); jni_Activity_setTitle = env->GetMethodID(jni_Activity, @@ -144,6 +148,28 @@ void JNI_OnUnload(JavaVM *jvm, void *reserved) { } } +/** + * + */ +Filename android_find_library(ANativeActivity *activity, const std::string &lib) { + Thread *thread = Thread::get_current_thread(); + JNIEnv *env = thread->get_jni_env(); + nassertr(env != nullptr, Filename()); + + jstring jlib = env->NewStringUTF(lib.c_str()); + jstring jresult = (jstring)env->CallObjectMethod(activity->clazz, jni_PandaActivity_findLibrary, jlib); + env->DeleteLocalRef(jlib); + + Filename result; + if (jresult != nullptr) { + const char *c_str = env->GetStringUTFChars(jresult, nullptr); + result = c_str; + env->ReleaseStringUTFChars(jresult, c_str); + } + + return result; +} + /** * Sets the window title of the activity. */ diff --git a/panda/src/android/config_android.h b/panda/src/android/config_android.h index 71d42af3f3..286e517a39 100644 --- a/panda/src/android/config_android.h +++ b/panda/src/android/config_android.h @@ -42,6 +42,7 @@ extern jclass jni_BitmapFactory_Options; extern jfieldID jni_BitmapFactory_Options_outWidth; extern jfieldID jni_BitmapFactory_Options_outHeight; +EXPORT_CLASS Filename android_find_library(ANativeActivity *activity, const std::string &lib); EXPORT_CLASS void android_set_title(ANativeActivity *activity, const std::string &title); EXPORT_CLASS void android_show_toast(ANativeActivity *activity, const std::string &message, int duration); diff --git a/pandatool/src/deploy-stub/android_main.cxx b/pandatool/src/deploy-stub/android_main.cxx index 082fadf463..685062c5ef 100644 --- a/pandatool/src/deploy-stub/android_main.cxx +++ b/pandatool/src/deploy-stub/android_main.cxx @@ -50,8 +50,8 @@ extern "C" { } blobinfo = {(uint64_t)-1}; } -// Defined in android_log.c -extern "C" PyObject *PyInit_android_log(); +// Defined in android_support.cxx +extern "C" PyObject *PyInit_android_support(); /** * Maps the binary blob at the given memory address to memory, and returns the @@ -250,7 +250,7 @@ void android_main(struct android_app *app) { if (dst_moddef->code != nullptr) { dst_moddef->code = (unsigned char *)((uintptr_t)dst_moddef->code + (uintptr_t)blob); } - __android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", dst_moddef->name, (void*)dst_moddef->code, dst_moddef->size); + //__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", dst_moddef->name, (void*)dst_moddef->code, dst_moddef->size); dst_moddef++; } PyImport_FrozenModules = new_modules; @@ -265,10 +265,10 @@ void android_main(struct android_app *app) { return; } - // Register the android_log module. - if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) { + // Register the android_support module. + if (PyImport_AppendInittab("android_support", &PyInit_android_support) < 0) { android_cat.error() - << "Failed to register android_log module.\n"; + << "Failed to register android_support module.\n"; env->ReleaseStringUTFChars(libdir_jstr, libdir); return; } @@ -279,6 +279,7 @@ void android_main(struct android_app *app) { config.buffered_stdio = 0; config.configure_c_stdio = 0; config.write_bytecode = 0; + config.module_search_paths_set = 1; // Leave sys.path empty PyConfig_SetBytesString(&config, &config.platlibdir, libdir); env->ReleaseStringUTFChars(libdir_jstr, libdir); diff --git a/pandatool/src/deploy-stub/android_log.c b/pandatool/src/deploy-stub/android_support.cxx similarity index 50% rename from pandatool/src/deploy-stub/android_log.c rename to pandatool/src/deploy-stub/android_support.cxx index 83b6090111..d24a8def93 100644 --- a/pandatool/src/deploy-stub/android_log.c +++ b/pandatool/src/deploy-stub/android_support.cxx @@ -6,7 +6,7 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file android_log.c + * @file android_support.c * @author rdb * @date 2021-12-10 */ @@ -17,12 +17,14 @@ #include "Python.h" #include +#include "android_native_app_glue.h" +#include "config_android.h" /** * Writes a message to the Android log. */ static PyObject * -_py_write(PyObject *self, PyObject *args) { +_py_log_write(PyObject *self, PyObject *args) { int prio; char *tag; char *text; @@ -33,14 +35,32 @@ _py_write(PyObject *self, PyObject *args) { return NULL; } +/** + * Returns the path to a library, if it can be found. + */ +static PyObject * +_py_find_library(PyObject *self, PyObject *args) { + char *lib; + if (PyArg_ParseTuple(args, "s", &lib)) { + Filename result = android_find_library(panda_android_app->activity, lib); + if (!result.empty()) { + return PyUnicode_FromStringAndSize(result.c_str(), (Py_ssize_t)result.length()); + } else { + Py_RETURN_NONE; + } + } + return NULL; +} + static PyMethodDef python_simple_funcs[] = { - { "write", &_py_write, METH_VARARGS }, + { "log_write", &_py_log_write, METH_VARARGS }, + { "find_library", &_py_find_library, METH_VARARGS }, { NULL, NULL } }; -static struct PyModuleDef android_log_module = { +static struct PyModuleDef android_support_module = { PyModuleDef_HEAD_INIT, - "android_log", + "android_support", NULL, -1, python_simple_funcs, @@ -48,6 +68,6 @@ static struct PyModuleDef android_log_module = { }; __attribute__((visibility("default"))) -PyObject *PyInit_android_log() { - return PyModule_Create(&android_log_module); +extern "C" PyObject *PyInit_android_support() { + return PyModule_Create(&android_support_module); }