diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 8b7f7373877d6a83f181b88ba96172a5508b89e9..7fe497c535b226207f86dd92bfcaaee12d4a092a 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -178,5 +178,7 @@ jobs: run: mpirun -n 4 -oversubscribe python3 -m unittest discover -v -s python - name: Run Python examples run: scripts/run_python_examples.sh - - name: Build a catalogue - run: build-catalogue -v default mechanisms/default + - name: Build and test a catalogue + run: | + build-catalogue -v default mechanisms/default + ./scripts/test-catalogue.py ./default-catalogue.so diff --git a/CMakeLists.txt b/CMakeLists.txt index a2d3ce9076b014027a5df4a855b1feb212bbd05b..ccdfe0db062bc3ce52c1fca5df02608a4fdf3191 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,13 +186,7 @@ install(TARGETS arbor-config-defs EXPORT arbor-targets) # Interface library `arbor-private-deps` collects dependencies, options etc. # for the arbor library. add_library(arbor-private-deps INTERFACE) -target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123) -if (UNIX) - target_compile_definitions(arbor-config-defs INTERFACE ARB_HAVE_DL) - target_link_libraries(arbor-private-deps INTERFACE ${CMAKE_DL_LIBS}) -else() - message(FATAL_ERROR "No support for dynamic loading on current platform.") -endif () +target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123 ${CMAKE_DL_LIBS}) install(TARGETS arbor-private-deps EXPORT arbor-targets) # Interface library `arborenv-private-deps` collects dependencies, options etc. diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt index 9296d2ac8d22d3bd4b6e076e220852fe75937791..b4c1b99479d971ce6f9c91f04aa702d8bf287ee3 100644 --- a/arbor/CMakeLists.txt +++ b/arbor/CMakeLists.txt @@ -55,6 +55,7 @@ set(arbor_sources threading/threading.cpp thread_private_spike_store.cpp tree.cpp + util/dylib.cpp util/hostname.cpp util/unwind.cpp version.cpp diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp index 829339fd39adf5ce96a543b746accb4c607b1945..3796b46aa45b0c1ab820f0dca3132e3b378da989 100644 --- a/arbor/mechcat.cpp +++ b/arbor/mechcat.cpp @@ -12,10 +12,11 @@ #include <arbor/mechanism.hpp> #include <arbor/util/expected.hpp> -#include "util/rangeutil.hpp" +#include "util/dylib.hpp" #include "util/maputil.hpp" +#include "util/rangeutil.hpp" #include "util/span.hpp" -#include "util/dl.hpp" +#include "util/strprintf.hpp" /* Notes on implementation: * @@ -587,18 +588,17 @@ const mechanism_catalogue& load_catalogue(const std::string& fn) { typedef const void* global_catalogue_t(); global_catalogue_t* get_catalogue = nullptr; try { - auto plugin = dl_open(fn); - get_catalogue = dl_get_symbol<global_catalogue_t*>(plugin, "get_catalogue"); - } catch(dl_error& e) { + get_catalogue = util::dl_get_symbol<global_catalogue_t*>(fn, "get_catalogue"); + } catch(util::dl_error& e) { throw bad_catalogue_error{e.what(), {e}}; } if (!get_catalogue) { throw bad_catalogue_error{util::pprintf("Unusable symbol 'get_catalogue' in shared object '{}'", fn)}; } - /* NOTE We do not free the DSO handle here and accept retaining the handles - until termination since the mechanisms provided by the catalogue may have - a different lifetime than the actual catalogue itfself. This is not a - leak proper as `dlopen` caches handles for us. + /* The DSO handle is not freed here: handles will be retained until + * termination since the mechanisms provided by the catalogue may have a + * different lifetime than the actual catalogue itfself. This is not a leak, + * as `dlopen` caches handles for us. */ return *((const mechanism_catalogue*)get_catalogue()); } diff --git a/arbor/util/dl.hpp b/arbor/util/dl.hpp deleted file mode 100644 index 8454036eb75fbb4b34294baab962535c39db407b..0000000000000000000000000000000000000000 --- a/arbor/util/dl.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#ifdef ARB_HAVE_DL -#include "util/dl_platform_posix.hpp" -#endif diff --git a/arbor/util/dl_platform_posix.hpp b/arbor/util/dl_platform_posix.hpp deleted file mode 100644 index e2442c15d0191c8beea7676ec46c2a8905eef84f..0000000000000000000000000000000000000000 --- a/arbor/util/dl_platform_posix.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include <fstream> -#include <string> - -#include <dlfcn.h> - -#include <arbor/arbexcept.hpp> - -#include "util/strprintf.hpp" - -namespace arb { - -struct dl_error: arbor_exception { - dl_error(const std::string& msg): arbor_exception{msg} {} -}; - -struct dl_handle { - void* dl = nullptr; -}; - -inline -dl_handle dl_open(const std::string& fn) { - // Test if we can open file - if (!std::ifstream{fn.c_str()}.good()) throw file_not_found_error{fn}; - // Call once to clear errors not caused by us - dlerror(); - // Try to open shared object - auto result = dlopen(fn.c_str(), RTLD_LAZY); - // dlopen fails by returning NULL - if (!result) throw dl_error{util::pprintf("[POSIX] dl_open failed with: {}", dlerror())}; - return {result}; -} - -template<typename T> inline -T dl_get_symbol(const dl_handle& handle, const std::string& symbol) { - // Call once to clear errors not caused by us - dlerror(); - // Get symbol from shared object, may return NULL if that is what symbol refers to - auto result = dlsym(handle.dl, symbol.c_str()); - // ... so we can only ask for dlerror here - if (auto error = dlerror()) throw dl_error{util::pprintf("[POSIX] dl_get_symbol failed with: {}", error)}; - return reinterpret_cast<T>(result); -} - -inline -void dl_close(dl_handle& handle) { - dlclose(handle.dl); - handle.dl = nullptr; -} - -} diff --git a/arbor/util/dylib.cpp b/arbor/util/dylib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f4826dfbdd570f89d89cd4df7bfe3a675f3c5d5 --- /dev/null +++ b/arbor/util/dylib.cpp @@ -0,0 +1,50 @@ +#include <fstream> +#include <string> + +#include <dlfcn.h> + +#include <arbor/arbexcept.hpp> + +#include "util/dylib.hpp" +#include "util/strprintf.hpp" + +namespace arb { +namespace util { + +void* dl_open(const std::string& fn) { + try { + std::ifstream fd{fn.c_str()}; + if(!fd.good()) throw file_not_found_error{fn}; + } catch(...) { + throw file_not_found_error{fn}; + } + // Call once to clear errors not caused by us + dlerror(); + auto result = dlopen(fn.c_str(), RTLD_LAZY); + // dlopen fails by returning NULL + if (nullptr == result) { + auto error = dlerror(); + throw dl_error{util::pprintf("[POSIX] dl_open failed with: {}", error)}; + } + return result; +} + +namespace impl{ +void* dl_get_symbol(const std::string& fn, const std::string& symbol) { + // Call once to clear errors not caused by us + dlerror(); + + auto handle = dl_open(fn); + + // Get symbol from shared object, may return NULL if that is what symbol refers to + auto result = dlsym(handle, symbol.c_str()); + // dlsym mayb return NULL even if succeeding + if (auto error = dlerror()) { + throw dl_error{util::pprintf("[POSIX] dl_get_symbol failed with: {}", error)}; + } + return result; +} +} // namespace impl + +} // namespace util +} // namespace arb diff --git a/arbor/util/dylib.hpp b/arbor/util/dylib.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ba97976a6a82966dbadf766330cf7d397af0053d --- /dev/null +++ b/arbor/util/dylib.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <string> + +#include <arbor/arbexcept.hpp> + +namespace arb { +namespace util { + +namespace impl{ +void* dl_get_symbol(const std::string& filename, const std::string& symbol); +} // namespace impl + +struct dl_error: arbor_exception { + dl_error(const std::string& msg): arbor_exception{msg} {} +}; + +// Find and return a symbol from a dynamic library with filename. +// Throws dl_error on error. +template<typename T> +T dl_get_symbol(const std::string& filename, const std::string& symbol) { + return reinterpret_cast<T>(impl::dl_get_symbol(filename, symbol)); +} + +} // namespace util +} // namespace arbor diff --git a/scripts/test-catalogue.py b/scripts/test-catalogue.py new file mode 100755 index 0000000000000000000000000000000000000000..7c4716d03e427be8fe9b1f2bc05e0ea8b5fa49ef --- /dev/null +++ b/scripts/test-catalogue.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + +import arbor + +P = argparse.ArgumentParser(description='Verify that a mechanism catalogue can be loaded through Python interface.') +P.add_argument('catname', metavar='FILE', help='path of the catalogue to test.') + +args = P.parse_args() +catname = args.catname + +print(catname) + +if not os.path.isfile(catname): + print('ERROR: unable to open catalogue file') + sys.exit(1) + +print([n for n in arbor.load_catalogue(catname).keys()])