diff --git a/src/config/config.cpp.in b/src/config/config.cpp.in index 09a4da97c7..d0eee8b093 100644 --- a/src/config/config.cpp.in +++ b/src/config/config.cpp.in @@ -7,14 +7,27 @@ #include "config/config.h" +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#elif defined(__APPLE__) +#include +#include +#else +#endif + +namespace fs = std::filesystem; + /// Git version of the project const std::string nmodl::Version::GIT_REVISION = "@NMODL_GIT_REVISION@"; /// NMODL version const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; -const std::string nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRARY_SUFFIX@"; - /** * \brief Path of nrnutils.lib file * @@ -23,5 +36,77 @@ const std::string nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRA * from CMAKE_INSTALL_PREFIX. Note that this use of NMODL_PROJECT_BINARY_DIR * will cause ccache misses when the build prefix is changed. */ -std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = - {"@CMAKE_INSTALL_PREFIX@/share/nmodl/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nmodl/nrnunits.lib"}; +const std::vector nmodl::PathHelper::BASE_SEARCH_PATHS = + {"@CMAKE_INSTALL_PREFIX@", "@NMODL_PROJECT_BINARY_DIR@"}; + +const std::string nmodl::PathHelper::SHARED_LIBRARY_PREFIX = "@CMAKE_SHARED_LIBRARY_PREFIX@"; +const std::string nmodl::PathHelper::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRARY_SUFFIX@"; + +namespace { + +std::string maybe_from_env(const std::string& varname) { + const auto value = std::getenv(varname.c_str()); + if (value != nullptr) { + return value; + } + +#if defined(_WIN32) + std::vector buffer; + DWORD copied = 0; + do { + buffer.resize(buffer.size() + MAX_PATH); + copied = GetModuleFileName(0, &buffer.at(0), buffer.size()); + } while (copied >= buffer.size()); + buffer.resize(copied); + fs::path executable(std::wstring(buffer.begin(), buffer.end())); +#elif defined(__APPLE__) + char buffer[PATH_MAX + 1]; + uint32_t bufsize = PATH_MAX + 1; + if( _NSGetExecutablePath(buffer, &bufsize) != 0) { + return ""; + } + auto executable = fs::path(buffer); +#else + auto executable = fs::read_symlink("/proc/self/exe"); +#endif + + auto executable_dir = fs::weakly_canonical(executable).parent_path(); + if (executable_dir.filename() == "bin") { + return executable_dir.parent_path().string(); + } else { + // On Windows, we may find ourselves in the top-level directory without a bin/ + return executable_dir.string(); + } +} + +} + +const std::string nmodl::PathHelper::NMODL_HOME = maybe_from_env("NMODL_HOME"); + +std::string nmodl::PathHelper::get_path(const std::string& what, bool is_library) { + std::vector search_paths = BASE_SEARCH_PATHS; + if (!NMODL_HOME.empty()) { + search_paths.emplace(search_paths.begin(), NMODL_HOME); + } + + // check paths in order and return if found + for (const auto& path: search_paths) { + auto full_path = fs::path(path); + if (is_library) { + full_path /= SHARED_LIBRARY_PREFIX + what + SHARED_LIBRARY_SUFFIX; + } else { + full_path /= what; + } + std::ifstream f(full_path); + if (f.good()) { + return full_path; + } + } + std::ostringstream err_msg; + err_msg << "Could not find '" << what << "' in any of:\n"; + for (const auto& path: search_paths) { + err_msg << "\t" << path << "\n"; + } + err_msg << "Please try setting the NMODLHOME environment variable\n"; + throw std::runtime_error(err_msg.str()); +} diff --git a/src/config/config.h b/src/config/config.h index df5d7d379a..40310adc83 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -15,9 +15,6 @@ * \brief Version information and units file path */ -#include -#include -#include #include #include @@ -44,38 +41,38 @@ struct Version { /** * \brief Information of units database i.e. `nrnunits.lib` */ -struct NrnUnitsLib { - /// paths where nrnunits.lib can be found - static std::vector NRNUNITSLIB_PATH; +class PathHelper { + /// pre-defined paths to search for files + const static std::vector BASE_SEARCH_PATHS; + /// prefix to use when looking for libraries + const static std::string SHARED_LIBRARY_PREFIX; + + /// suffix to use when looking for libraries + const static std::string SHARED_LIBRARY_SUFFIX; + + /// base directory of the NMODL installation + const static std::string NMODL_HOME; + + /** + * Search for a given relative file path + */ + static std::string get_path(const std::string& what, bool is_library = false); + + public: /** * Return path of units database file */ - static std::string get_path() { - // first look for NMODLHOME env variable - if (const char* nmodl_home = std::getenv("NMODLHOME")) { - auto path = std::string(nmodl_home) + "/share/nmodl/nrnunits.lib"; - NRNUNITSLIB_PATH.emplace(NRNUNITSLIB_PATH.begin(), path); - } - - // check paths in order and return if found - for (const auto& path: NRNUNITSLIB_PATH) { - std::ifstream f(path.c_str()); - if (f.good()) { - return path; - } - } - std::ostringstream err_msg; - err_msg << "Could not find nrnunits.lib in any of:\n"; - for (const auto& path: NRNUNITSLIB_PATH) { - err_msg << path << "\n"; - } - throw std::runtime_error(err_msg.str()); - } -}; + static std::string get_units_path() { + return get_path("share/nmodl/nrnunits.lib"); + }; -struct CMakeInfo { - static const std::string SHARED_LIBRARY_SUFFIX; + /** + * Return path of the python wrapper library + */ + static std::string get_wrapper_path() { + return get_path("lib/libpywrapper", true); + }; }; } // namespace nmodl diff --git a/src/main.cpp b/src/main.cpp index be322c0e99..891476389e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,7 +142,7 @@ int main(int argc, const char* argv[]) { std::string scratch_dir("tmp"); /// directory where units lib file is located - std::string units_dir(NrnUnitsLib::get_path()); + std::string units_dir(PathHelper::get_units_path()); /// true if ast should be converted to json bool json_ast(false); diff --git a/src/parser/main_units.cpp b/src/parser/main_units.cpp index 6c6966aae0..95ce6946ff 100644 --- a/src/parser/main_units.cpp +++ b/src/parser/main_units.cpp @@ -28,7 +28,7 @@ int main(int argc, const char* argv[]) { fmt::format("Unit-Parser : Standalone Parser for Units({})", Version::to_string())}; std::vector units_files; - units_files.push_back(NrnUnitsLib::get_path()); + units_files.push_back(PathHelper::get_units_path()); app.add_option("units_files", units_files, "One or more Units files to process"); CLI11_PARSE(app, argc, argv); diff --git a/src/pybind/pyembed.cpp b/src/pybind/pyembed.cpp index f6e9b5e49c..1c38813d88 100644 --- a/src/pybind/pyembed.cpp +++ b/src/pybind/pyembed.cpp @@ -81,22 +81,11 @@ void EmbeddedPythonLoader::load_libraries() { assert_compatible_python_versions(); - if (std::getenv("NMODLHOME") == nullptr) { - logger->critical("NMODLHOME environment variable must be set to load embedded python"); - throw std::runtime_error("NMODLHOME not set"); - } - auto pybind_wraplib_env = fs::path(std::getenv("NMODLHOME")) / "lib" / "libpywrapper"; - pybind_wraplib_env.concat(CMakeInfo::SHARED_LIBRARY_SUFFIX); - if (!fs::exists(pybind_wraplib_env)) { - logger->critical("NMODLHOME doesn't contain libpywrapper{} library", - CMakeInfo::SHARED_LIBRARY_SUFFIX); - throw std::runtime_error("NMODLHOME doesn't have lib/libpywrapper library"); - } - std::string env_str = pybind_wraplib_env.string(); - pybind_wrapper_handle = dlopen(env_str.c_str(), dlopen_opts); + auto pybind_wraplib_env = PathHelper::get_wrapper_path(); + pybind_wrapper_handle = dlopen(pybind_wraplib_env.c_str(), dlopen_opts); if (!pybind_wrapper_handle) { const auto errstr = dlerror(); - logger->critical("Tried but failed to load {}", pybind_wraplib_env.string()); + logger->critical("Tried but failed to load {}", pybind_wraplib_env); logger->critical(errstr); throw std::runtime_error("Failed to dlopen"); } diff --git a/src/visitors/main.cpp b/src/visitors/main.cpp index 9a6b969663..0653f866e4 100644 --- a/src/visitors/main.cpp +++ b/src/visitors/main.cpp @@ -95,7 +95,7 @@ int main(int argc, const char* argv[]) { "SympyConductanceVisitor"}, {std::make_shared(), "sympy-solve", "SympySolverVisitor"}, {std::make_shared(), "neuron-solve", "NeuronSolveVisitor"}, - {std::make_shared(NrnUnitsLib::get_path()), "units", "UnitsVisitor"}, + {std::make_shared(PathHelper::get_units_path()), "units", "UnitsVisitor"}, }; const std::vector const_visitors = { diff --git a/test/unit/units/parser.cpp b/test/unit/units/parser.cpp index 3ffd3497e1..9d17d94e44 100644 --- a/test/unit/units/parser.cpp +++ b/test/unit/units/parser.cpp @@ -33,7 +33,7 @@ bool is_valid_construct(const std::string& construct) { std::string parse_string(const std::string& unit_definition) { nmodl::parser::UnitDriver correctness_driver; - correctness_driver.parse_file(nmodl::NrnUnitsLib::get_path()); + correctness_driver.parse_file(nmodl::PathHelper::get_units_path()); correctness_driver.parse_string(unit_definition); std::stringstream ss; correctness_driver.table->print_units_sorted(ss); diff --git a/test/unit/visitor/units.cpp b/test/unit/visitor/units.cpp index 06f3865cd8..4bfcdc5544 100644 --- a/test/unit/visitor/units.cpp +++ b/test/unit/visitor/units.cpp @@ -36,7 +36,7 @@ std::tuple, std::shared_ptr> run const auto& ast = driver.get_ast(); // Parse nrnunits.lib file and the UNITS block of the mod file - const std::string units_lib_path(NrnUnitsLib::get_path()); + const std::string units_lib_path(PathHelper::get_units_path()); UnitsVisitor units_visitor = UnitsVisitor(units_lib_path); units_visitor.visit_program(*ast);