calibre/bypy/run-python.h
2020-08-22 18:47:56 +05:30

189 lines
5.7 KiB
C

/*
* Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <sys/time.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <strings.h>
#include <errno.h>
#include <Python.h>
#ifdef __APPLE__
#include <os/log.h>
#endif
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
void
log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
static bool use_os_log = false;
void
log_error(const char *fmt, ...) {
va_list ar;
struct timeval tv;
#ifdef __APPLE__
// Apple does not provide a varargs style os_logv
char logbuf[16 * 1024] = {0};
#else
char logbuf[4];
#endif
char *p = logbuf;
#define bufprint(func, ...) { if ((size_t)(p - logbuf) < sizeof(logbuf) - 2) { p += func(p, sizeof(logbuf) - (p - logbuf), __VA_ARGS__); } }
if (!use_os_log) { // Apple's os_log already records timestamps
gettimeofday(&tv, NULL);
struct tm *tmp = localtime(&tv.tv_sec);
if (tmp) {
char tbuf[256] = {0}, buf[256] = {0};
if (strftime(buf, sizeof(buf), "%j %H:%M:%S.%%06u", tmp) != 0) {
snprintf(tbuf, sizeof(tbuf), buf, tv.tv_usec);
fprintf(stderr, "[%s] ", tbuf);
}
}
}
va_start(ar, fmt);
if (use_os_log) { bufprint(vsnprintf, fmt, ar); }
else vfprintf(stderr, fmt, ar);
va_end(ar);
#ifdef __APPLE__
if (use_os_log) os_log(OS_LOG_DEFAULT, "%{public}s", logbuf);
#endif
if (!use_os_log) fprintf(stderr, "\n");
}
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }
static void
set_sys_string(const char* key, const wchar_t* val) {
PyObject *temp = PyUnicode_FromWideChar(val, -1);
if (temp) {
if (PySys_SetObject(key, temp) != 0) fatal("Failed to set attribute on sys: %s", key);
Py_DECREF(temp);
} else {
fatal("Failed to set attribute on sys, PyUnicode_FromWideChar failed");
}
}
static void
set_sys_bool(const char* key, const bool val) {
if (PySys_SetObject(key, val ? Py_True : Py_False) != 0) fatal("Failed to set attribute on sys: %s", key);
}
static void
pre_initialize_interpreter(bool is_gui_app) {
PyStatus status;
use_os_log = is_gui_app;
PyPreConfig preconfig;
PyPreConfig_InitIsolatedConfig(&preconfig);
preconfig.utf8_mode = 1;
preconfig.coerce_c_locale = 1;
preconfig.isolated = 1;
status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status)) Py_ExitStatusException(status);
}
#define decode_char_buf(src, dest) { \
size_t tsz; \
wchar_t* t__ = Py_DecodeLocale(src, &tsz); \
if (!t__) fatal("Failed to decode path: %s", src); \
if (tsz > sizeof(dest) - 1) tsz = sizeof(dest) - 1; \
memcpy(dest, t__, tsz * sizeof(wchar_t)); \
dest[tsz] = 0; \
PyMem_RawFree(t__); \
}
#define MAX_SYS_PATHS 3
typedef struct {
wchar_t* sys_paths[MAX_SYS_PATHS];
wchar_t sys_path_buf[MAX_SYS_PATHS * PATH_MAX];
size_t sys_paths_count;
wchar_t exe_path[PATH_MAX], python_home_path[PATH_MAX], python_lib_path[PATH_MAX];
wchar_t extensions_path[PATH_MAX], resources_path[PATH_MAX], executables_path[PATH_MAX];
const wchar_t *basename, *module, *function;
int argc;
char * const *argv;
} InterpreterData;
static InterpreterData interpreter_data = {0};
static wchar_t*
add_sys_path() {
if (interpreter_data.sys_paths_count >= MAX_SYS_PATHS) fatal("Trying to add too many entries to sys.path");
wchar_t *ans = interpreter_data.sys_path_buf + PATH_MAX * interpreter_data.sys_paths_count;
interpreter_data.sys_paths[interpreter_data.sys_paths_count] = ans;
interpreter_data.sys_paths_count++;
return ans;
}
#ifdef _WIN32
#else
static void
add_sys_paths() {
swprintf(add_sys_path(), PATH_MAX, L"%ls", interpreter_data.python_lib_path);
swprintf(add_sys_path(), PATH_MAX, L"%ls/lib-dynload", interpreter_data.python_lib_path);
swprintf(add_sys_path(), PATH_MAX, L"%ls/site-packages", interpreter_data.python_lib_path);
}
#endif
static void
run_interpreter() {
#define CHECK_STATUS if (PyStatus_Exception(status)) { PyConfig_Clear(&config); Py_ExitStatusException(status); }
PyStatus status;
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
add_sys_paths();
status = PyConfig_SetWideStringList(&config, &config.module_search_paths, interpreter_data.sys_paths_count, interpreter_data.sys_paths);
CHECK_STATUS;
config.module_search_paths_set = 1;
config.optimization_level = 2;
config.write_bytecode = 0;
config.use_environment = 0;
config.user_site_directory = 0;
config.configure_c_stdio = 1;
config.isolated = 1;
status = PyConfig_SetString(&config, &config.program_name, interpreter_data.exe_path);
CHECK_STATUS;
status = PyConfig_SetString(&config, &config.home, interpreter_data.python_home_path);
CHECK_STATUS;
status = PyConfig_SetString(&config, &config.run_module, L"site");
CHECK_STATUS;
status = PyConfig_SetBytesArgv(&config, interpreter_data.argc, interpreter_data.argv);
CHECK_STATUS;
status = Py_InitializeFromConfig(&config);
CHECK_STATUS;
set_sys_bool("gui_app", use_os_log);
set_sys_bool("frozen", true);
set_sys_string("calibre_basename", interpreter_data.basename);
set_sys_string("calibre_module", interpreter_data.module);
set_sys_string("calibre_function", interpreter_data.function);
set_sys_string("extensions_location", interpreter_data.extensions_path);
set_sys_string("resources_location", interpreter_data.resources_path);
set_sys_string("executables_location", interpreter_data.executables_path);
#ifdef __APPLE__
#elif _WIN32
#else
set_sys_string("frozen_path", interpreter_data.executables_path);
#endif
int ret = Py_RunMain();
PyConfig_Clear(&config);
exit(ret);
#undef CHECK_STATUS
}