dist: Implement more reliable solution for logging stdout/stderr

The previous trick doesn't seem to work on one of my devices.  The disadvantage of the new solution, however, is that it doesn't capture anything until site.py is successfully executed.
This commit is contained in:
rdb 2021-12-10 13:44:01 +01:00
parent cc865e6d21
commit b8c301164a
4 changed files with 133 additions and 57 deletions

View File

@ -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)

View File

@ -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'])

View File

@ -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 <android/log.h>
/**
* 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);
}

View File

@ -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;