diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 308a96a910..2af5db4116 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -163,12 +163,57 @@ from _frozen_importlib import _imp, FrozenImporter from importlib import _bootstrap_external from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec +from io import RawIOBase, TextIOWrapper + +from android_log import write as android_log_write sys.frozen = True sys.platform = "android" +# Replace stdout/stderr with something that writes to the Android log. +class AndroidLogStream: + closed = False + encoding = 'utf-8' + + def __init__(self, prio, tag): + self.prio = prio + self.tag = tag + self.buffer = '' + + def isatty(self): + return False + + def write(self, text): + self.writelines(text.split('\\n')) + + def writelines(self, lines): + num_lines = len(lines) + if num_lines == 1: + self.buffer += lines[0] + elif num_lines > 1: + android_log_write(self.prio, self.tag, self.buffer + lines[0]) + for line in lines[1:-1]: + android_log_write(self.prio, self.tag, line) + self.buffer = lines[-1] + + def flush(self): + pass + + def seekable(self): + return False + + def readable(self): + return False + + def writable(self): + return True + +sys.stdout = AndroidLogStream(2, 'Python') +sys.stderr = AndroidLogStream(3, 'Python') + + # Alter FrozenImporter to give a __file__ property to frozen modules. _find_spec = FrozenImporter.find_spec @@ -191,7 +236,7 @@ class AndroidExtensionFinder(MetaPathFinder): @classmethod def find_spec(cls, fullname, path=None, target=None): soname = 'libpy.' + fullname + '.so' - path = os.path.join(sys._native_library_dir, soname) + path = os.path.join(os.path.dirname(sys.executable), soname) if os.path.exists(path): loader = _bootstrap_external.ExtensionFileLoader(fullname, path) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index ce7cc78a73..1ca21d97de 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6052,8 +6052,10 @@ if PkgSkip("PYTHON") == 0: PyTargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'DEPLOYSTUB', 'NOICON']) elif GetTarget() == 'android': 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('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=COMMON_PANDA_LIBS) PyTargetAdd('libdeploy-stubw.dll', input='libp3android.dll') PyTargetAdd('libdeploy-stubw.dll', opts=['DEPLOYSTUB', 'ANDROID']) diff --git a/pandatool/src/deploy-stub/android_log.c b/pandatool/src/deploy-stub/android_log.c new file mode 100644 index 0000000000..209515f90d --- /dev/null +++ b/pandatool/src/deploy-stub/android_log.c @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file android_log.c + * @author rdb + * @date 2021-12-10 + */ + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#define PY_SSIZE_T_CLEAN 1 + +#include "Python.h" +#include + +/** + * Writes a message to the Android log. + */ +static PyObject * +_py_write(PyObject *self, PyObject *args) { + int prio; + char *tag; + char *text; + if (PyArg_ParseTuple(args, "iss", &prio, &tag, &text)) { + __android_log_write(prio, tag, text); + Py_INCREF(Py_None); + return Py_None; + } + return NULL; + +} + +static PyMethodDef python_simple_funcs[] = { + { "write", &_py_write, METH_VARARGS }, + { NULL, NULL } +}; + +static struct PyModuleDef android_log_module = { + PyModuleDef_HEAD_INIT, + "android_log", + NULL, + -1, + python_simple_funcs, + NULL, NULL, NULL, NULL +}; + +__attribute__((visibility("default"))) +PyObject *PyInit_android_log() { + return PyModule_Create(&android_log_module); +} diff --git a/pandatool/src/deploy-stub/android_main.cxx b/pandatool/src/deploy-stub/android_main.cxx index 3adcfa4a8f..05f4f09c5b 100644 --- a/pandatool/src/deploy-stub/android_main.cxx +++ b/pandatool/src/deploy-stub/android_main.cxx @@ -50,6 +50,9 @@ extern "C" { } blobinfo = {(uint64_t)-1}; } +// Defined in android_log.c +extern "C" PyObject *PyInit_android_log(); + /** * Maps the binary blob at the given memory address to memory, and returns the * pointer to the beginning of it. @@ -96,36 +99,6 @@ void android_main(struct android_app *app) { return; } - // Pipe stdout/stderr to the Android log stream, for convenience. - int pfd[2]; - setvbuf(stdout, 0, _IOLBF, 0); - setvbuf(stderr, 0, _IOLBF, 0); - - pipe(pfd); - dup2(pfd[1], 1); - dup2(pfd[1], 2); - - std::thread t([=] { - ssize_t size; - char buf[4096] = {0}; - char *bufstart = buf; - char *const bufend = buf + sizeof(buf) - 1; - - while ((size = read(pfd[0], bufstart, bufend - bufstart)) > 0) { - bufstart[size] = 0; - bufstart += size; - - while (char *nl = (char *)memchr(buf, '\n', strnlen(buf, bufend - buf))) { - *nl = 0; - __android_log_write(ANDROID_LOG_VERBOSE, "Python", buf); - - // Move everything after the newline to the beginning of the buffer. - memmove(buf, nl + 1, bufend - (nl + 1)); - bufstart -= (nl + 1) - buf; - } - } - }); - jclass activity_class = env->GetObjectClass(activity->clazz); // Get the current Java thread name. This just helps with debugging. @@ -179,6 +152,19 @@ void android_main(struct android_app *app) { env->ReleaseStringUTFChars(jcache_dir, cache_dir); } + // Fetch the path to the library directory. + jfieldID libdir_field = env->GetFieldID(appinfo_class, "nativeLibraryDir", "Ljava/lang/String;"); + jstring libdir_jstr = (jstring) env->GetObjectField(appinfo, libdir_field); + const char *libdir = env->GetStringUTFChars(libdir_jstr, nullptr); + + if (libdir != nullptr) { + std::string dtool_name = std::string(libdir) + "/libp3dtool.so"; + ExecutionEnvironment::set_dtool_name(dtool_name); + android_cat.info() << "Path to dtool: " << dtool_name << "\n"; + + env->ReleaseStringUTFChars(libdir_jstr, libdir); + } + // Get the path to the APK. methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;"); jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID); @@ -198,7 +184,6 @@ void android_main(struct android_app *app) { // Map the blob to memory void *blob = map_blob(lib_path, (off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); - env->ReleaseStringUTFChars(lib_path_jstr, lib_path); assert(blob != NULL); assert(blobinfo.num_pointers <= MAX_NUM_POINTERS); @@ -261,12 +246,21 @@ void android_main(struct android_app *app) { return; } + // Register the android_log module. + if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) { + android_cat.error() + << "Failed to register android_log module.\n"; + return; + } + PyConfig config; PyConfig_InitIsolatedConfig(&config); config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */ config.buffered_stdio = 0; config.configure_c_stdio = 0; config.write_bytecode = 0; + PyConfig_SetBytesString(&config, &config.executable, lib_path); + env->ReleaseStringUTFChars(lib_path_jstr, lib_path); status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); @@ -275,26 +269,6 @@ void android_main(struct android_app *app) { return; } - // Fetch the path to the library directory. - jfieldID libdir_field = env->GetFieldID(appinfo_class, "nativeLibraryDir", "Ljava/lang/String;"); - jstring libdir_jstr = (jstring) env->GetObjectField(appinfo, libdir_field); - const char *libdir = env->GetStringUTFChars(libdir_jstr, nullptr); - - if (libdir != nullptr) { - // This is used by the import hook to locate the module libraries. - PyObject *py_native_dir = PyUnicode_FromString(libdir); - PySys_SetObject("_native_library_dir", py_native_dir); - Py_DECREF(py_native_dir); - - if (ExecutionEnvironment::get_dtool_name().empty()) { - std::string dtool_name = std::string(libdir) + "/libp3dtool.so"; - ExecutionEnvironment::set_dtool_name(dtool_name); - android_cat.info() << "Path to dtool: " << dtool_name << "\n"; - } - - env->ReleaseStringUTFChars(libdir_jstr, libdir); - } - while (!app->destroyRequested) { // Call the main module. This will not return until the app is done. android_cat.info() << "Importing __main__\n"; @@ -305,13 +279,13 @@ void android_main(struct android_app *app) { break; } if (n < 0) { - PyErr_Print(); + if (_PyErr_OCCURRED() != PyExc_SystemExit) { + PyErr_Print(); + } else { + PyErr_Clear(); + } } - fsync(1); - fsync(2); - sched_yield(); - if (app->destroyRequested) { // The app closed responding to a destroy request. break;