NEURON
pyembed.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 #include "pybind/pyembed.hpp"
8 
9 #include <cstdlib>
10 #include <dlfcn.h>
11 #include <filesystem>
12 #include <pybind11/embed.h>
13 
14 
15 #include "config/config.h"
16 #include "utils/logger.hpp"
17 
18 #define STRINGIFY(x) #x
19 #define TOSTRING(x) STRINGIFY(x)
20 
21 namespace fs = std::filesystem;
22 
23 namespace nmodl {
24 
25 namespace pybind_wrappers {
26 
27 
29 
31 #if defined(NMODL_STATIC_PYWRAPPER)
33 #else
35  "nmodl_init_pybind_wrapper_api"));
36 #endif
37 
38  if (init != nullptr) {
39  wrappers = init();
40  }
41 
42  return init != nullptr;
43 }
44 
46  // This code is imported and slightly modified from PyBind11 because this
47  // is primarly in details for internal usage
48  // License of PyBind11 is BSD-style
49 
50  std::string compiled_ver = fmt::format("{}.{}", PY_MAJOR_VERSION, PY_MINOR_VERSION);
51  auto pPy_GetVersion = (const char* (*) (void) ) dlsym(RTLD_DEFAULT, "Py_GetVersion");
52  if (pPy_GetVersion == nullptr) {
53  throw std::runtime_error("Unable to find the function `Py_GetVersion`");
54  }
55  const char* runtime_ver = pPy_GetVersion();
56  std::size_t len = compiled_ver.size();
57  if (std::strncmp(runtime_ver, compiled_ver.c_str(), len) != 0 ||
58  (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) {
59  throw std::runtime_error(
60  fmt::format("Python version mismatch. nmodl has been compiled with python {} and is "
61  "being run with python {}",
62  compiled_ver,
63  runtime_ver));
64  }
65 }
66 
68  const auto pylib_env = std::getenv("NMODL_PYLIB");
69  if (!pylib_env) {
70  logger->critical("NMODL_PYLIB environment variable must be set to load embedded python");
71  throw std::runtime_error("NMODL_PYLIB not set");
72  }
73  const auto dlopen_opts = RTLD_NOW | RTLD_GLOBAL;
74  dlerror(); // reset old error conditions
75  pylib_handle = dlopen(pylib_env, dlopen_opts);
76  if (!pylib_handle) {
77  const auto errstr = dlerror();
78  logger->critical("Tried but failed to load {}", pylib_env);
79  logger->critical(errstr);
80  throw std::runtime_error("Failed to dlopen");
81  }
82 
84 
85  if (std::getenv("NMODLHOME") == nullptr) {
86  logger->critical("NMODLHOME environment variable must be set to load embedded python");
87  throw std::runtime_error("NMODLHOME not set");
88  }
89  auto pybind_wraplib_env = fs::path(std::getenv("NMODLHOME")) / "lib" / "libpywrapper";
90  pybind_wraplib_env.concat(CMakeInfo::SHARED_LIBRARY_SUFFIX);
91  if (!fs::exists(pybind_wraplib_env)) {
92  logger->critical("NMODLHOME doesn't contain libpywrapper{} library",
94  throw std::runtime_error("NMODLHOME doesn't have lib/libpywrapper library");
95  }
96  std::string env_str = pybind_wraplib_env.string();
97  pybind_wrapper_handle = dlopen(env_str.c_str(), dlopen_opts);
98  if (!pybind_wrapper_handle) {
99  const auto errstr = dlerror();
100  logger->critical("Tried but failed to load {}", pybind_wraplib_env.string());
101  logger->critical(errstr);
102  throw std::runtime_error("Failed to dlopen");
103  }
104 }
105 
107 #if defined(NMODL_STATIC_PYWRAPPER)
109 #else
110  // By now it's been dynamically loaded with `RTLD_GLOBAL`.
112  "nmodl_init_pybind_wrapper_api"));
113 #endif
114 
115  if (!init) {
116  const auto errstr = dlerror();
117  logger->critical("Tried but failed to load pybind wrapper symbols");
118  logger->critical(errstr);
119  throw std::runtime_error("Failed to dlsym");
120  }
121 
122  wrappers = init();
123 }
124 
126  if (pybind_wrapper_handle) {
128  pybind_wrapper_handle = nullptr;
129  }
130  if (pylib_handle) {
132  pylib_handle = nullptr;
133  }
134 }
135 
137  return wrappers;
138 }
139 
140 
141 } // namespace pybind_wrappers
142 
143 } // namespace nmodl
const pybind_wrap_api & api()
Get a pointer to the pybind_wrap_api struct.
Definition: pyembed.cpp:136
DLFCN_EXPORT void * dlopen(const char *file, int mode)
Definition: dlfcn.c:331
DLFCN_EXPORT char * dlerror(void)
Definition: dlfcn.c:548
DLFCN_EXPORT int dlclose(void *handle)
Definition: dlfcn.c:423
DLFCN_NOINLINE DLFCN_EXPORT void * dlsym(void *handle, const char *name)
Definition: dlfcn.c:447
#define RTLD_DEFAULT
Definition: dlfcn.h:66
#define RTLD_NOW
Definition: dlfcn.h:47
#define RTLD_GLOBAL
Definition: dlfcn.h:56
void init()
Definition: init.cpp:141
decltype(&nmodl_init_pybind_wrapper_api) nmodl_init_pybind_wrapper_api_fpointer
Definition: pyembed.cpp:28
void assert_compatible_python_versions()
Definition: pyembed.cpp:45
NMODL_EXPORT pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept
Definition: wrapper.cpp:244
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
logger_type logger
Definition: logger.cpp:34
static const std::string SHARED_LIBRARY_SUFFIX
Definition: config.h:78