User Guide
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 
28 
30 #if defined(NMODL_STATIC_PYWRAPPER)
31  auto* init = &nmodl_init_pybind_wrapper_api;
32 #else
33  auto* init = (nmodl_init_pybind_wrapper_api_fpointer) (dlsym(RTLD_DEFAULT,
34  "nmodl_init_pybind_wrapper_api"));
35 #endif
36 
37  if (init != nullptr) {
38  wrappers = init();
39  }
40 
41  return init != nullptr;
42 }
43 
45  // This code is imported and slightly modified from PyBind11 because this
46  // is primarly in details for internal usage
47  // License of PyBind11 is BSD-style
48 
49  std::string compiled_ver = fmt::format("{}.{}", PY_MAJOR_VERSION, PY_MINOR_VERSION);
50  auto pPy_GetVersion = (const char* (*) (void) ) dlsym(RTLD_DEFAULT, "Py_GetVersion");
51  if (pPy_GetVersion == nullptr) {
52  throw std::runtime_error("Unable to find the function `Py_GetVersion`");
53  }
54  const char* runtime_ver = pPy_GetVersion();
55  std::size_t len = compiled_ver.size();
56  if (std::strncmp(runtime_ver, compiled_ver.c_str(), len) != 0 ||
57  (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) {
58  throw std::runtime_error(
59  fmt::format("Python version mismatch. nmodl has been compiled with python {} and is "
60  "being run with python {}",
61  compiled_ver,
62  runtime_ver));
63  }
64 }
65 
67  const auto pylib_env = std::getenv("NMODL_PYLIB");
68  if (!pylib_env) {
69  logger->critical("NMODL_PYLIB environment variable must be set to load embedded python");
70  throw std::runtime_error("NMODL_PYLIB not set");
71  }
72  const auto dlopen_opts = RTLD_NOW | RTLD_GLOBAL;
73  dlerror(); // reset old error conditions
74  pylib_handle = dlopen(pylib_env, dlopen_opts);
75  if (!pylib_handle) {
76  const auto errstr = dlerror();
77  logger->critical("Tried but failed to load {}", pylib_env);
78  logger->critical(errstr);
79  throw std::runtime_error("Failed to dlopen");
80  }
81 
83 
84  if (std::getenv("NMODLHOME") == nullptr) {
85  logger->critical("NMODLHOME environment variable must be set to load embedded python");
86  throw std::runtime_error("NMODLHOME not set");
87  }
88  auto pybind_wraplib_env = fs::path(std::getenv("NMODLHOME")) / "lib" / "libpywrapper";
89  pybind_wraplib_env.concat(CMakeInfo::SHARED_LIBRARY_SUFFIX);
90  if (!fs::exists(pybind_wraplib_env)) {
91  logger->critical("NMODLHOME doesn't contain libpywrapper{} library",
93  throw std::runtime_error("NMODLHOME doesn't have lib/libpywrapper library");
94  }
95  std::string env_str = pybind_wraplib_env.string();
96  pybind_wrapper_handle = dlopen(env_str.c_str(), dlopen_opts);
97  if (!pybind_wrapper_handle) {
98  const auto errstr = dlerror();
99  logger->critical("Tried but failed to load {}", pybind_wraplib_env.string());
100  logger->critical(errstr);
101  throw std::runtime_error("Failed to dlopen");
102  }
103 }
104 
106 #if defined(NMODL_STATIC_PYWRAPPER)
107  auto* init = &nmodl_init_pybind_wrapper_api;
108 #else
109  // By now it's been dynamically loaded with `RTLD_GLOBAL`.
110  auto* init = (nmodl_init_pybind_wrapper_api_fpointer) (dlsym(RTLD_DEFAULT,
111  "nmodl_init_pybind_wrapper_api"));
112 #endif
113 
114  if (!init) {
115  const auto errstr = dlerror();
116  logger->critical("Tried but failed to load pybind wrapper symbols");
117  logger->critical(errstr);
118  throw std::runtime_error("Failed to dlsym");
119  }
120 
121  wrappers = init();
122 }
123 
125  if (pybind_wrapper_handle) {
126  dlclose(pybind_wrapper_handle);
127  pybind_wrapper_handle = nullptr;
128  }
129  if (pylib_handle) {
130  dlclose(pylib_handle);
131  pylib_handle = nullptr;
132  }
133 }
134 
136  return wrappers;
137 }
138 
139 
140 } // namespace pybind_wrappers
141 
142 } // namespace nmodl
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
nmodl::pybind_wrappers::EmbeddedPythonLoader::pylib_handle
void * pylib_handle
Definition: pyembed.hpp:54
nmodl::logger
logger_type logger
Definition: logger.cpp:34
nmodl::pybind_wrappers::EmbeddedPythonLoader::load_libraries
void load_libraries()
Definition: pyembed.cpp:66
nmodl::pybind_wrappers::assert_compatible_python_versions
void assert_compatible_python_versions()
Definition: pyembed.cpp:44
nmodl::pybind_wrappers::EmbeddedPythonLoader::populate_symbols
void populate_symbols()
Definition: pyembed.cpp:105
nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX
static const std::string SHARED_LIBRARY_SUFFIX
Definition: config.h:78
nmodl::pybind_wrappers::EmbeddedPythonLoader::api
const pybind_wrap_api & api()
Get a pointer to the pybind_wrap_api struct.
Definition: pyembed.cpp:135
nmodl::pybind_wrappers::EmbeddedPythonLoader::unload
void unload()
Definition: pyembed.cpp:124
nmodl::pybind_wrappers::EmbeddedPythonLoader::pybind_wrapper_handle
void * pybind_wrapper_handle
Definition: pyembed.hpp:55
logger.hpp
Implement logger based on spdlog library.
nmodl::pybind_wrappers::pybind_wrap_api
Definition: wrapper.hpp:60
config.h
Version information and units file path.
nmodl::pybind_wrappers::nmodl_init_pybind_wrapper_api
NMODL_EXPORT pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept
Definition: wrapper.cpp:244
nmodl::pybind_wrappers::nmodl_init_pybind_wrapper_api_fpointer
decltype(&nmodl_init_pybind_wrapper_api) nmodl_init_pybind_wrapper_api_fpointer
Definition: pyembed.cpp:27
nmodl::pybind_wrappers::EmbeddedPythonLoader::wrappers
pybind_wrap_api wrappers
Definition: pyembed.hpp:52
nmodl::pybind_wrappers::EmbeddedPythonLoader::have_wrappers
bool have_wrappers()
Definition: pyembed.cpp:29
pyembed.hpp