|
14 | 14 | #include "PyModuleRegistry.h" |
15 | 15 |
|
16 | 16 | #include <filesystem> |
| 17 | +#include <boost/scope_exit.hpp> |
17 | 18 |
|
18 | 19 | #include "include/stringify.h" |
19 | 20 | #include "common/errno.h" |
@@ -46,14 +47,74 @@ void PyModuleRegistry::init() |
46 | 47 |
|
47 | 48 | // Set up global python interpreter |
48 | 49 | #define WCHAR(s) L ## #s |
| 50 | +#if PY_VERSION_HEX >= 0x03080000 |
| 51 | + PyConfig py_config; |
| 52 | + // do not enable isolated mode, otherwise we would not be able to have access |
| 53 | + // to the site packages. since we cannot import any module before initializing |
| 54 | + // the interpreter, we would not be able to use "site" module for retrieving |
| 55 | + // the path to site packager. we import "site" module for retrieving |
| 56 | + // sitepackages in Python < 3.8 though, this does not apply to the |
| 57 | + // initialization with PyConfig. |
| 58 | + PyConfig_InitPythonConfig(&py_config); |
| 59 | + BOOST_SCOPE_EXIT_ALL(&py_config) { |
| 60 | + PyConfig_Clear(&py_config); |
| 61 | + }; |
| 62 | +#if PY_VERSION_HEX >= 0x030b0000 |
| 63 | + py_config.safe_path = 0; |
| 64 | +#endif |
| 65 | + py_config.parse_argv = 0; |
| 66 | + py_config.configure_c_stdio = 0; |
| 67 | + py_config.install_signal_handlers = 0; |
| 68 | + py_config.pathconfig_warnings = 0; |
| 69 | + // site_import is 1 by default, but set it explicitly as we do import |
| 70 | + // site packages manually for Python < 3.8 |
| 71 | + py_config.site_import = 1; |
| 72 | + |
| 73 | + PyStatus status; |
| 74 | + status = PyConfig_SetString(&py_config, &py_config.program_name, WCHAR(MGR_PYTHON_EXECUTABLE)); |
| 75 | + ceph_assertf(!PyStatus_Exception(status), "PyConfig_SetString: %s:%s", status.func, status.err_msg); |
| 76 | + // Some python modules do not cope with an unpopulated argv, so lets |
| 77 | + // fake one. This step also picks up site-packages into sys.path. |
| 78 | + const wchar_t* argv[] = {L"ceph-mgr"}; |
| 79 | + status = PyConfig_SetArgv(&py_config, 1, (wchar_t *const *)argv); |
| 80 | + ceph_assertf(!PyStatus_Exception(status), "PyConfig_SetArgv: %s:%s", status.func, status.err_msg); |
| 81 | + // Add more modules |
| 82 | + if (g_conf().get_val<bool>("daemonize")) { |
| 83 | + PyImport_AppendInittab("ceph_logger", PyModule::init_ceph_logger); |
| 84 | + } |
| 85 | + PyImport_AppendInittab("ceph_module", PyModule::init_ceph_module); |
| 86 | + // Configure sys.path to include mgr_module_path |
| 87 | + auto pythonpath_env = g_conf().get_val<std::string>("mgr_module_path"); |
| 88 | + if (const char* pythonpath = getenv("PYTHONPATH")) { |
| 89 | + pythonpath_env += ":"; |
| 90 | + pythonpath_env += pythonpath; |
| 91 | + } |
| 92 | + status = PyConfig_SetBytesString(&py_config, &py_config.pythonpath_env, pythonpath_env.data()); |
| 93 | + ceph_assertf(!PyStatus_Exception(status), "PyConfig_SetBytesString: %s:%s", status.func, status.err_msg); |
| 94 | + dout(10) << "set PYTHONPATH to " << std::quoted(pythonpath_env) << dendl; |
| 95 | + status = Py_InitializeFromConfig(&py_config); |
| 96 | + ceph_assertf(!PyStatus_Exception(status), "Py_InitializeFromConfig: %s:%s", status.func, status.err_msg); |
| 97 | +#else |
49 | 98 | Py_SetProgramName(const_cast<wchar_t*>(WCHAR(MGR_PYTHON_EXECUTABLE))); |
50 | | -#undef WCHAR |
51 | 99 | // Add more modules |
52 | 100 | if (g_conf().get_val<bool>("daemonize")) { |
53 | 101 | PyImport_AppendInittab("ceph_logger", PyModule::init_ceph_logger); |
54 | 102 | } |
55 | 103 | PyImport_AppendInittab("ceph_module", PyModule::init_ceph_module); |
56 | 104 | Py_InitializeEx(0); |
| 105 | + const wchar_t *argv[] = {L"ceph-mgr"}; |
| 106 | + PySys_SetArgv(1, (wchar_t**)argv); |
| 107 | + |
| 108 | + std::string paths = (g_conf().get_val<std::string>("mgr_module_path") + ':' + |
| 109 | + get_site_packages() + ':'); |
| 110 | + std::wstring sys_path(begin(paths), end(paths)); |
| 111 | + sys_path += Py_GetPath(); |
| 112 | + PySys_SetPath(const_cast<wchar_t*>(sys_path.c_str())); |
| 113 | + dout(10) << "Computed sys.path '" |
| 114 | + << std::string(begin(sys_path), end(sys_path)) << "'" << dendl; |
| 115 | +#endif // PY_VERSION_HEX >= 0x03080000 |
| 116 | +#undef WCHAR |
| 117 | + |
57 | 118 | #if PY_VERSION_HEX < 0x03090000 |
58 | 119 | // Let CPython know that we will be calling it back from other |
59 | 120 | // threads in future. |
@@ -217,6 +278,72 @@ void PyModuleRegistry::active_start( |
217 | 278 | } |
218 | 279 | } |
219 | 280 |
|
| 281 | +std::string PyModuleRegistry::get_site_packages() |
| 282 | +{ |
| 283 | + std::stringstream site_packages; |
| 284 | + |
| 285 | + // CPython doesn't auto-add site-packages dirs to sys.path for us, |
| 286 | + // but it does provide a module that we can ask for them. |
| 287 | + auto site_module = PyImport_ImportModule("site"); |
| 288 | + ceph_assert(site_module); |
| 289 | + |
| 290 | + auto site_packages_fn = PyObject_GetAttrString(site_module, "getsitepackages"); |
| 291 | + if (site_packages_fn != nullptr) { |
| 292 | + auto site_packages_list = PyObject_CallObject(site_packages_fn, nullptr); |
| 293 | + ceph_assert(site_packages_list); |
| 294 | + |
| 295 | + auto n = PyList_Size(site_packages_list); |
| 296 | + for (Py_ssize_t i = 0; i < n; ++i) { |
| 297 | + if (i != 0) { |
| 298 | + site_packages << ":"; |
| 299 | + } |
| 300 | + site_packages << PyUnicode_AsUTF8(PyList_GetItem(site_packages_list, i)); |
| 301 | + } |
| 302 | + |
| 303 | + Py_DECREF(site_packages_list); |
| 304 | + Py_DECREF(site_packages_fn); |
| 305 | + } else { |
| 306 | + // Fall back to generating our own site-packages paths by imitating |
| 307 | + // what the standard site.py does. This is annoying but it lets us |
| 308 | + // run inside virtualenvs :-/ |
| 309 | + |
| 310 | + auto site_packages_fn = PyObject_GetAttrString(site_module, "addsitepackages"); |
| 311 | + ceph_assert(site_packages_fn); |
| 312 | + |
| 313 | + auto known_paths = PySet_New(nullptr); |
| 314 | + auto pArgs = PyTuple_Pack(1, known_paths); |
| 315 | + PyObject_CallObject(site_packages_fn, pArgs); |
| 316 | + Py_DECREF(pArgs); |
| 317 | + Py_DECREF(known_paths); |
| 318 | + Py_DECREF(site_packages_fn); |
| 319 | + |
| 320 | + auto sys_module = PyImport_ImportModule("sys"); |
| 321 | + ceph_assert(sys_module); |
| 322 | + auto sys_path = PyObject_GetAttrString(sys_module, "path"); |
| 323 | + ceph_assert(sys_path); |
| 324 | + |
| 325 | + dout(1) << "sys.path:" << dendl; |
| 326 | + auto n = PyList_Size(sys_path); |
| 327 | + bool first = true; |
| 328 | + for (Py_ssize_t i = 0; i < n; ++i) { |
| 329 | + dout(1) << " " << PyUnicode_AsUTF8(PyList_GetItem(sys_path, i)) << dendl; |
| 330 | + if (first) { |
| 331 | + first = false; |
| 332 | + } else { |
| 333 | + site_packages << ":"; |
| 334 | + } |
| 335 | + site_packages << PyUnicode_AsUTF8(PyList_GetItem(sys_path, i)); |
| 336 | + } |
| 337 | + |
| 338 | + Py_DECREF(sys_path); |
| 339 | + Py_DECREF(sys_module); |
| 340 | + } |
| 341 | + |
| 342 | + Py_DECREF(site_module); |
| 343 | + |
| 344 | + return site_packages.str(); |
| 345 | +} |
| 346 | + |
220 | 347 | std::vector<std::string> PyModuleRegistry::probe_modules(const std::string &path) const |
221 | 348 | { |
222 | 349 | const auto opt = g_conf().get_val<std::string>("mgr_disabled_modules"); |
|
0 commit comments