From aabaa87c7d9232277dd6a2ab5c206f56df3086e3 Mon Sep 17 00:00:00 2001 From: Ben Cumming <bcumming@cscs.ch> Date: Fri, 26 Nov 2021 11:10:29 +0100 Subject: [PATCH] Test separately built catalogues (#1748) * add a test that tests that a catalogue built separately can be loaded via the Python interface * further simplification of dynamic library support * move all platform-specific code into the cpp implementation and out of header. --- .github/workflows/basic.yml | 6 ++-- CMakeLists.txt | 8 +---- arbor/CMakeLists.txt | 1 + arbor/mechcat.cpp | 18 +++++------ arbor/util/dl.hpp | 5 --- arbor/util/dl_platform_posix.hpp | 52 -------------------------------- arbor/util/dylib.cpp | 50 ++++++++++++++++++++++++++++++ arbor/util/dylib.hpp | 26 ++++++++++++++++ scripts/test-catalogue.py | 21 +++++++++++++ 9 files changed, 112 insertions(+), 75 deletions(-) delete mode 100644 arbor/util/dl.hpp delete mode 100644 arbor/util/dl_platform_posix.hpp create mode 100644 arbor/util/dylib.cpp create mode 100644 arbor/util/dylib.hpp create mode 100755 scripts/test-catalogue.py diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 8b7f7373..7fe497c5 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 a2d3ce90..ccdfe0db 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 9296d2ac..b4c1b994 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 829339fd..3796b46a 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 8454036e..00000000 --- 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 e2442c15..00000000 --- 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 00000000..3f4826df --- /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 00000000..ba97976a --- /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 00000000..7c4716d0 --- /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()]) -- GitLab