ComIn 0.5.1
ICON Community Interface
Loading...
Searching...
No Matches
python_adapter.cpp
Go to the documentation of this file.
1/* @authors 11/2023 :: ICON Community Interface <comin@icon-model.org>
2
3 SPDX-License-Identifier: BSD-3-Clause
4
5 Please see the file LICENSE in the root of the source tree for this code.
6 Where software is supplied by third parties, it is indicated in the
7 headers of the routines. */
8
9#define PY_SSIZE_T_CLEAN
10#include <Python.h>
11
12#include <array>
13#include <cstdlib>
14#include <filesystem>
15#include <iostream>
16#include <map>
17#include <sstream>
18#include <string>
19#include <vector>
20
21#include <dlfcn.h>
22
23#include "comin.h"
24#include "util.h"
25
26#include "comin.py.h"
27#include "config.h"
28
29#include "callbacks.h"
30#include "descrdata.h"
31#include "exception.h"
32#include "variables.h"
33
34namespace comin::python {
35
36static struct PyModuleDef module = {
37 PyModuleDef_HEAD_INIT, "_comin", /* name of module */
38 NULL, /* module documentation, may be NULL */
39 -1, /* size of per-interpreter state of the module,
40 or -1 if the module keeps state in global variables. */
41 NULL /*m_methods - will be set in main before calling PyModule_Create*/
42};
43
44static std::vector<PyMethodDef> pyCominMethods;
45
46PyMODINIT_FUNC PyInit_comin(void) {
47 // collect methods from other cpp files:
48 for (const auto& m : callbacks_methods())
49 pyCominMethods.push_back(m);
50 for (const auto& m : variables_methods())
51 pyCominMethods.push_back(m);
52 for (const auto& m : descrdata_methods())
53 pyCominMethods.push_back(m);
54 pyCominMethods.push_back({NULL, NULL, 0, NULL}); /* Sentinel */
55
56 module.m_methods = pyCominMethods.data();
57 PyObject* pSelf = PyModule_Create(&module);
58 if (pSelf == NULL)
59 return NULL;
60
62 PyErr_NewException("comin.ComInError", PyExc_RuntimeError, NULL);
63 Py_INCREF(PyExc_ComInError);
64 PyModule_AddObject(pSelf, "ComInError", PyExc_ComInError);
65
66 return pSelf;
67}
68
69static char* extract_filename(const char* str) {
70 PyObject* pShlex = PyImport_ImportModule("shlex");
71 PyObject* pDict = PyModule_GetDict(pShlex); // returns a borrowed reference
72 PyObject* pSplit = PyDict_GetItemString(
73 pDict, (char*)"split"); // returns a borrowed reference
74 PyObject* pList = PyObject_CallFunction(pSplit, "s", str);
75 PyObject* pFilename =
76 PyList_GetItem(pList, 0); // returns a borrowed reference
77 char* filename = strdup(PyUnicode_AsUTF8(pFilename));
78 Py_DECREF(pList);
79 Py_DECREF(pShlex);
80 return filename;
81}
82
83static std::string exec(std::string cmd) {
84 std::array<char, 128> buffer;
85 std::string result;
86 std::unique_ptr<FILE, void (*)(FILE*)> pipe(
87 popen(cmd.c_str(), "r"),
88 [](FILE* f) -> void { std::ignore = pclose(f); });
89 if (!pipe) {
90 comin_plugin_finish_f("python_adapter",
91 "Cannot execute given python executable: %s",
92 cmd.c_str());
93 }
94 while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe.get()) !=
95 nullptr) {
96 result += buffer.data();
97 }
98 return result;
99}
100
101static void python_version_check(std::string python_exe) {
102 std::string py_version_str = exec(python_exe + " --version");
103 // assuming that the output is something like "Python 3.13.0"
104 py_version_str =
105 py_version_str.substr(py_version_str.find(" ") + 1); // truncate "Python "
106 int major =
107 std::atoi(py_version_str.substr(0, py_version_str.find(".")).c_str());
108 py_version_str =
109 py_version_str.substr(py_version_str.find(".") + 1); // truncate "3."
110 int minor =
111 std::atoi(py_version_str.substr(0, py_version_str.find(".") + 1).c_str());
112 if (major != PY_MAJOR_VERSION || minor != PY_MINOR_VERSION) {
113 comin_plugin_finish_f("python_adapter::python_version_check",
114 "Versions of the embedded interpreter (%d.%d)"
115 " and the python executable (%d.%d)"
116 " are not compatible.",
117 PY_MAJOR_VERSION, PY_MINOR_VERSION, major, minor);
118 }
119}
120
121static void initialize_python() {
122 PyStatus status;
123 PyConfig config;
124 PyConfig_InitPythonConfig(&config);
125 std::string python3_exe = Python3_EXECUTABLE;
126 if (const char* python3_exe_env = std::getenv("COMIN_PYTHON_EXECUTABLE"))
127 python3_exe = python3_exe_env;
128
129 python_version_check(python3_exe);
130
131 status =
132 PyConfig_SetBytesString(&config, &config.executable, python3_exe.c_str());
133 if (PyStatus_Exception(status)) {
135 "python_adapter::comin_main",
136 "Cannot set config.executable for python initialization. %s",
137 status.err_msg);
138 return;
139 }
140
141 status = Py_InitializeFromConfig(&config);
142 if (PyStatus_Exception(status)) {
143 comin_plugin_finish_f("python_adapter::comin_main",
144 "Python initialization failed. (%s)", status.err_msg);
145 return;
146 }
147 PyConfig_Clear(&config);
148}
149
150static int instance_counter = 0;
151
152extern "C" {
153 void comin_main() {
154
155 using namespace std::string_literals;
156
157 int mpi_rank = comin_parallel_get_host_mpi_rank();
158 if (mpi_rank == 0)
159 std::cerr << "setup_python_adapter" << std::endl;
160
161 if (!Py_IsInitialized()) {
162 // this is a workaround for a python problem that occurs if python
163 // is embedded in a library that is loaded with dlopen.
164 // See for example:
165 // https://bugs.python.org/issue4434
166 // https://stackoverflow.com/questions/8302810/undefined-symbol-in-c-when-loading-a-python-shared-library
167 // https://stackoverflow.com/questions/64295279/so-type-plugin-with-embedded-python-interpreter-how-to-link-load-libpython
168 char libname[17];
169 sprintf(libname, "libpython3.%d.so", PY_MINOR_VERSION);
170 void* handle = dlopen(libname, RTLD_LAZY | RTLD_GLOBAL);
171 if (handle == nullptr) {
172 if (mpi_rank == 0) {
174 __func__, "Plugin cannot be loaded.\nLibrary: %s\ndlerror: %d",
175 libname, dlerror);
176 }
177 } else {
178 comin_print_debug_f("Python adapter: %s loaded!", libname);
179 }
180
181 dlclose(handle);
182
183 PyImport_AppendInittab("_comin", &PyInit_comin);
184
186
187 std::string comin_py_c((char*)comin_py, comin_py_len);
188 PyObject* compiled_comin =
189 Py_CompileString(comin_py_c.c_str(), "comin.py", Py_file_input);
190 if (PyErr_Occurred()) {
191 PyErr_Print();
192 comin_plugin_finish(__func__, "Cannot compile comin module");
193 }
194 PyImport_ExecCodeModule("comin", compiled_comin);
195 if (PyErr_Occurred()) {
196 PyErr_Print();
197 comin_plugin_finish(__func__, "Cannot load comin module");
198 }
199 }
200
202 comin_callback_register(EP_DESTRUCTOR, []() {
205 if (instance_counter == 0)
206 Py_Finalize();
207 });
208
209 // Dictionary to store the plugins global variables
210 // independently from other python plugins
211 PyObject* globals = PyDict_New();
212
214
215 // add directory of filename to sys.path
216 std::filesystem::path fpath = filename;
217 auto parent_path = std::filesystem::canonical(fpath.parent_path());
218 PyRun_SimpleString(
219 ("import sys; sys.path.insert(0,\""s + parent_path.c_str() + "\")"s)
220 .c_str());
221
222 // errors need to be handled by check_error
224 try {
225 if (mpi_rank == 0)
226 std::cerr << "Running python script " << filename << std::endl;
227 FILE* file = fopen(filename, "r");
228 if (file == NULL)
229 throw std::runtime_error("Cannot read "s + std::string(filename));
230 else {
231 PyObject* pyResult =
232 PyRun_File(file, filename, Py_file_input, globals, globals);
233 Py_XDECREF(pyResult);
234 fclose(file);
235 if (PyErr_Occurred()) {
236 PyErr_Print();
237 throw std::runtime_error("Error while executing "s +
238 std::string(filename));
239 }
240 PyRun_SimpleString(
241 ("sys.path.remove(\""s + parent_path.c_str() + "\")"s).c_str());
242 }
243 } catch (std::exception& err) {
244 comin_plugin_finish_f("python_adapter::comin_main",
245 "Error while executing script:\n%s", err.what());
246 return;
247 }
248 }
249}
250} // namespace comin::python
C interface for the ICON Community Interface.
void comin_print_debug_f(const char *fmt,...)
void comin_plugin_finish_f(const char *routine, const char *fmt,...)
void comin_plugin_finish(const char *routine, const char *text)
void comin_error_set_errors_return(bool errors_return)
const char * comin_current_get_plugin_options()
void comin_callback_register(t_comin_entry_point entry_point, t_comin_callback_function fct_ptr)
static void initialize_python()
static char * extract_filename(const char *str)
PyMODINIT_FUNC PyInit_comin(void)
static struct PyModuleDef module
PyObject * PyExc_ComInError
Definition exception.cpp:5
void generic_callback()
Definition callbacks.cpp:32
static int instance_counter
const std::vector< PyMethodDef > descrdata_methods()
std::vector< PyMethodDef > variables_methods()
static void python_version_check(std::string python_exe)
static std::vector< PyMethodDef > pyCominMethods
std::vector< PyMethodDef > callbacks_methods()
Definition callbacks.cpp:95
static std::string exec(std::string cmd)
double * buffer