• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <android/log.h>
2 #include <errno.h>
3 #include <jni.h>
4 #include <pthread.h>
5 #include <Python.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <unistd.h>
9 
10 
throw_runtime_exception(JNIEnv * env,const char * message)11 static void throw_runtime_exception(JNIEnv *env, const char *message) {
12     (*env)->ThrowNew(
13         env,
14         (*env)->FindClass(env, "java/lang/RuntimeException"),
15         message);
16 }
17 
18 
19 // --- Stdio redirection ------------------------------------------------------
20 
21 // Most apps won't need this, because the Python-level sys.stdout and sys.stderr
22 // are redirected to the Android logcat by Python itself. However, in the
23 // testbed it's useful to redirect the native streams as well, to debug problems
24 // in the Python startup or redirection process.
25 //
26 // Based on
27 // https://github.com/beeware/briefcase-android-gradle-template/blob/v0.3.11/%7B%7B%20cookiecutter.safe_formal_name%20%7D%7D/app/src/main/cpp/native-lib.cpp
28 
29 typedef struct {
30     FILE *file;
31     int fd;
32     android_LogPriority priority;
33     char *tag;
34     int pipe[2];
35 } StreamInfo;
36 
37 static StreamInfo STREAMS[] = {
38     {stdout, STDOUT_FILENO, ANDROID_LOG_INFO, "native.stdout", {-1, -1}},
39     {stderr, STDERR_FILENO, ANDROID_LOG_WARN, "native.stderr", {-1, -1}},
40     {NULL, -1, ANDROID_LOG_UNKNOWN, NULL, {-1, -1}},
41 };
42 
43 // The maximum length of a log message in bytes, including the level marker and
44 // tag, is defined as LOGGER_ENTRY_MAX_PAYLOAD in
45 // platform/system/logging/liblog/include/log/log.h. As of API level 30, messages
46 // longer than this will be be truncated by logcat. This limit has already been
47 // reduced at least once in the history of Android (from 4076 to 4068 between API
48 // level 23 and 26), so leave some headroom.
49 static const int MAX_BYTES_PER_WRITE = 4000;
50 
redirection_thread(void * arg)51 static void *redirection_thread(void *arg) {
52     StreamInfo *si = (StreamInfo*)arg;
53     ssize_t read_size;
54     char buf[MAX_BYTES_PER_WRITE];
55     while ((read_size = read(si->pipe[0], buf, sizeof buf - 1)) > 0) {
56         buf[read_size] = '\0'; /* add null-terminator */
57         __android_log_write(si->priority, si->tag, buf);
58     }
59     return 0;
60 }
61 
redirect_stream(StreamInfo * si)62 static char *redirect_stream(StreamInfo *si) {
63     /* make the FILE unbuffered, to ensure messages are never lost */
64     if (setvbuf(si->file, 0, _IONBF, 0)) {
65         return "setvbuf";
66     }
67 
68     /* create the pipe and redirect the file descriptor */
69     if (pipe(si->pipe)) {
70         return "pipe";
71     }
72     if (dup2(si->pipe[1], si->fd) == -1) {
73         return "dup2";
74     }
75 
76     /* start the logging thread */
77     pthread_t thr;
78     if ((errno = pthread_create(&thr, 0, redirection_thread, si))) {
79         return "pthread_create";
80     }
81     if ((errno = pthread_detach(thr))) {
82         return "pthread_detach";
83     }
84     return 0;
85 }
86 
Java_org_python_testbed_PythonTestRunner_redirectStdioToLogcat(JNIEnv * env,jobject obj)87 JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToLogcat(
88     JNIEnv *env, jobject obj
89 ) {
90     for (StreamInfo *si = STREAMS; si->file; si++) {
91         char *error_prefix;
92         if ((error_prefix = redirect_stream(si))) {
93             char error_message[1024];
94             snprintf(error_message, sizeof(error_message),
95                      "%s: %s", error_prefix, strerror(errno));
96             throw_runtime_exception(env, error_message);
97             return;
98         }
99     }
100 }
101 
102 
103 // --- Python initialization ---------------------------------------------------
104 
set_config_string(JNIEnv * env,PyConfig * config,wchar_t ** config_str,jstring value)105 static PyStatus set_config_string(
106     JNIEnv *env, PyConfig *config, wchar_t **config_str, jstring value
107 ) {
108     const char *value_utf8 = (*env)->GetStringUTFChars(env, value, NULL);
109     PyStatus status = PyConfig_SetBytesString(config, config_str, value_utf8);
110     (*env)->ReleaseStringUTFChars(env, value, value_utf8);
111     return status;
112 }
113 
throw_status(JNIEnv * env,PyStatus status)114 static void throw_status(JNIEnv *env, PyStatus status) {
115     throw_runtime_exception(env, status.err_msg ? status.err_msg : "");
116 }
117 
Java_org_python_testbed_PythonTestRunner_runPython(JNIEnv * env,jobject obj,jstring home,jstring runModule)118 JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython(
119     JNIEnv *env, jobject obj, jstring home, jstring runModule
120 ) {
121     PyConfig config;
122     PyStatus status;
123     PyConfig_InitIsolatedConfig(&config);
124 
125     status = set_config_string(env, &config, &config.home, home);
126     if (PyStatus_Exception(status)) {
127         throw_status(env, status);
128         return 1;
129     }
130 
131     status = set_config_string(env, &config, &config.run_module, runModule);
132     if (PyStatus_Exception(status)) {
133         throw_status(env, status);
134         return 1;
135     }
136 
137     // Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
138     config.install_signal_handlers = 1;
139 
140     status = Py_InitializeFromConfig(&config);
141     if (PyStatus_Exception(status)) {
142         throw_status(env, status);
143         return 1;
144     }
145 
146     return Py_RunMain();
147 }
148