diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0bd94e50722a6b15e10e1ee1f6f7090d303d7387..da5ac435be809258e42f59ce8a2737c4e0d172ed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -355,8 +355,6 @@ endif()
 #--------------
 
 if(ARB_WITH_GPU)
-    target_compile_definitions(arbor-config-defs INTERFACE ARB_HAVE_GPU)
-
     if(ARB_WITH_NVCC OR ARB_WITH_CUDA_CLANG)
         target_include_directories(arborenv-private-deps INTERFACE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
         add_compile_options(
diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp
index 7ede9f7c8fe816da055c78e240b1a8c5c4761e29..65d0a61a0e19b3c9b5a527b212764bd69b9d284d 100644
--- a/arbor/fvm_layout.cpp
+++ b/arbor/fvm_layout.cpp
@@ -808,7 +808,7 @@ fvm_mechanism_data fvm_build_mechanism_data(
 
         fvm_mechanism_config config;
         if (info.kind != arb_mechanism_kind_density) {
-            throw cable_cell_error("expected density mechanism, got " +name +" which has " +arb_mechsnism_kind_str(info.kind));
+            throw cable_cell_error("expected density mechanism, got " +name +" which has " +arb_mechanism_kind_str(info.kind));
         }
         config.kind = arb_mechanism_kind_density;
 
@@ -918,7 +918,7 @@ fvm_mechanism_data fvm_build_mechanism_data(
         mechanism_info info = catalogue[name];
 
         if (info.kind != arb_mechanism_kind_point) {
-            throw cable_cell_error("expected point mechanism, got " +name +" which has " +arb_mechsnism_kind_str(info.kind));
+            throw cable_cell_error("expected point mechanism, got " +name +" which has " +arb_mechanism_kind_str(info.kind));
         }
 
         post_events |= info.post_events;
@@ -1077,7 +1077,7 @@ fvm_mechanism_data fvm_build_mechanism_data(
     for (const auto& [name, placements]: cell.junctions()) {
         mechanism_info info = catalogue[name];
         if (info.kind != arb_mechanism_kind_gap_junction) {
-            throw cable_cell_error("expected gap_junction mechanism, got " +name +" which has " +arb_mechsnism_kind_str(info.kind));
+            throw cable_cell_error("expected gap_junction mechanism, got " +name +" which has " +arb_mechanism_kind_str(info.kind));
         }
 
         fvm_mechanism_config config;
@@ -1268,7 +1268,7 @@ fvm_mechanism_data fvm_build_mechanism_data(
             const mechanism_desc& revpot = *maybe_revpot;
             mechanism_info info = catalogue[revpot.name()];
             if (info.kind != arb_mechanism_kind_reversal_potential) {
-                throw cable_cell_error("expected reversal potential mechanism for ion " +ion +", got "+ revpot.name() +" which has " +arb_mechsnism_kind_str(info.kind));
+                throw cable_cell_error("expected reversal potential mechanism for ion " +ion +", got "+ revpot.name() +" which has " +arb_mechanism_kind_str(info.kind));
             }
 
             verify_mechanism(info, revpot);
diff --git a/arbor/fvm_lowered_cell_impl.cpp b/arbor/fvm_lowered_cell_impl.cpp
index 01b0ee0fd7eaa5fb0a96db3a59f11fcb11ca7fcb..9e521a9e36d433004cdcae3fa8a943149ef9b667 100644
--- a/arbor/fvm_lowered_cell_impl.cpp
+++ b/arbor/fvm_lowered_cell_impl.cpp
@@ -3,9 +3,10 @@
 
 #include <arbor/arbexcept.hpp>
 #include <arbor/common_types.hpp>
+#include <arbor/version.hpp>
 
 #include "backends/multicore/fvm.hpp"
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 #include "backends/gpu/fvm.hpp"
 #endif
 #include "fvm_lowered_cell_impl.hpp"
@@ -17,7 +18,7 @@ fvm_lowered_cell_ptr make_fvm_lowered_cell(backend_kind p, const execution_conte
     case backend_kind::multicore:
         return fvm_lowered_cell_ptr(new fvm_lowered_cell_impl<multicore::backend>(ctx));
     case backend_kind::gpu:
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
         return fvm_lowered_cell_ptr(new fvm_lowered_cell_impl<gpu::backend>(ctx));
 #endif
         ; // fall through
diff --git a/arbor/gpu_context.cpp b/arbor/gpu_context.cpp
index baad4acfea3028fc96593d87738cdae4cb65d245..ff1dbde0f1a659cf061931ff7ce2db00bfc9c802 100644
--- a/arbor/gpu_context.cpp
+++ b/arbor/gpu_context.cpp
@@ -1,10 +1,10 @@
 #include <memory>
 
 #include <arbor/arbexcept.hpp>
-
+#include <arbor/version.hpp>
 #include "gpu_context.hpp"
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 #include <arbor/gpu/gpu_api.hpp>
 #endif
 
@@ -30,7 +30,7 @@ bool gpu_context::has_gpu() const {
     return id_ != -1;
 }
 
-#ifndef ARB_HAVE_GPU
+#ifndef ARB_GPU_ENABLED
 
 void gpu_context::set_gpu() const {
     throw arbor_exception("Arbor must be compiled with CUDA/HIP support to set a GPU.");
diff --git a/arbor/hardware/memory.cpp b/arbor/hardware/memory.cpp
index 4274ede47640a81846f117cba6944885a75eede8..f2370a68c0f37c348d78f83cd32c53b0fd0795ec 100644
--- a/arbor/hardware/memory.cpp
+++ b/arbor/hardware/memory.cpp
@@ -1,4 +1,5 @@
 #include "memory.hpp"
+#include <arbor/version.hpp>
 
 #ifdef __linux__
 extern "C" {
@@ -6,7 +7,7 @@ extern "C" {
 }
 #endif
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
     #include <arbor/gpu/gpu_api.hpp>
 #endif
 
diff --git a/arbor/include/arbor/mechanism_abi.h b/arbor/include/arbor/mechanism_abi.h
index 69e11c7c47cb5198b8dd2201a5f3ba9b8772c1e3..2998bc765d90f6d54957a4e83c1038dd43a39db7 100644
--- a/arbor/include/arbor/mechanism_abi.h
+++ b/arbor/include/arbor/mechanism_abi.h
@@ -9,8 +9,8 @@ extern "C" {
 
 // Version
 #define ARB_MECH_ABI_VERSION_MAJOR 0
-#define ARB_MECH_ABI_VERSION_MINOR 0
-#define ARB_MECH_ABI_VERSION_PATCH 1
+#define ARB_MECH_ABI_VERSION_MINOR 1
+#define ARB_MECH_ABI_VERSION_PATCH 0
 #define ARB_MECH_ABI_VERSION ((ARB_MECH_ABI_VERSION_MAJOR * 10000L * 10000L) + (ARB_MECH_ABI_VERSION_MAJOR * 10000L) + ARB_MECH_ABI_VERSION_PATCH)
 
 typedef const char* arb_mechanism_fingerprint;
@@ -28,7 +28,7 @@ typedef uint32_t arb_backend_kind;
 #define arb_backend_kind_cpu 1
 #define arb_backend_kind_gpu 2
 
-inline const char* arb_mechsnism_kind_str(const arb_mechanism_kind& mech) {
+inline const char* arb_mechanism_kind_str(const arb_mechanism_kind& mech) {
     switch (mech) {
         case arb_mechanism_kind_density: return "density mechanism kind";
         case arb_mechanism_kind_point:   return "point mechanism kind";
@@ -208,6 +208,16 @@ typedef struct arb_mechanism_type {
     arb_size_type             n_ions;
 } arb_mechanism_type;
 
+// Bundle a type and its interfaces
+typedef arb_mechanism_type (*arb_get_mechanism_type)();
+typedef arb_mechanism_interface* (*arb_get_mechanism_interface)();
+
+typedef struct arb_mechanism {
+    arb_get_mechanism_type type;
+    arb_get_mechanism_interface i_cpu;
+    arb_get_mechanism_interface i_gpu;
+} arb_mechanism;
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp
index c7811725bea6b7051292ef13a569048e6edbbf16..c8f98bd329b7e80c5b6d801b5f32c2f19565edab 100644
--- a/arbor/include/arbor/mechcat.hpp
+++ b/arbor/include/arbor/mechcat.hpp
@@ -115,6 +115,6 @@ ARB_ARBOR_API const mechanism_catalogue& global_allen_catalogue();
 ARB_ARBOR_API const mechanism_catalogue& global_bbp_catalogue();
 
 // Load catalogue from disk.
-ARB_ARBOR_API const mechanism_catalogue& load_catalogue(const std::string&);
+ARB_ARBOR_API const mechanism_catalogue load_catalogue(const std::string&);
 
 } // namespace arb
diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp
index 161c5f9a58253d770e65f8db721d08ac66de02eb..4fb428d6ead27c2856219c3eab89401067423673 100644
--- a/arbor/mechcat.cpp
+++ b/arbor/mechcat.cpp
@@ -592,8 +592,8 @@ std::pair<mechanism_ptr, mechanism_overrides> mechanism_catalogue::instance_impl
 
 mechanism_catalogue::~mechanism_catalogue() = default;
 
-ARB_ARBOR_API const mechanism_catalogue& load_catalogue(const std::string& fn) {
-    typedef const void* global_catalogue_t();
+ARB_ARBOR_API const mechanism_catalogue load_catalogue(const std::string& fn) {
+    typedef void* global_catalogue_t(int*);
     global_catalogue_t* get_catalogue = nullptr;
     try {
         get_catalogue = util::dl_get_symbol<global_catalogue_t*>(fn, "get_catalogue");
@@ -608,7 +608,32 @@ ARB_ARBOR_API const mechanism_catalogue& load_catalogue(const std::string& fn) {
      * different lifetime than the actual catalogue itfself. This is not a leak,
      * as `dlopen` caches handles for us.
      */
-    return *((const mechanism_catalogue*)get_catalogue());
+    int count = -1;
+    auto mechs = (arb_mechanism*)get_catalogue(&count);
+    if (count <= 0) {
+        throw bad_catalogue_error{util::pprintf("Invalid mechanism count {} in shared object '{}'", count, fn)};
+    }
+    mechanism_catalogue result;
+    for(int ix = 0; ix < count; ++ix) {
+        auto type = mechs[ix].type();
+        auto name = std::string{type.name};
+        if (name == "") {
+            throw bad_catalogue_error{util::pprintf("Empty name for mechanism in '{}'", fn)};
+        }
+        auto icpu = mechs[ix].i_cpu();
+        auto igpu = mechs[ix].i_gpu();
+        if (!icpu && !igpu) {
+            throw bad_catalogue_error{util::pprintf("Empty interfaces for mechanism '{}'", name)};
+        }
+        result.add(name, type);
+        if (icpu) {
+            result.register_implementation(name, std::make_unique<mechanism>(type, *icpu));
+        }
+        if (igpu) {
+            result.register_implementation(name, std::make_unique<mechanism>(type, *igpu));
+        }
+    }
+    return result;
 }
 
 } // namespace arb
diff --git a/arbor/memory/gpu_wrappers.cpp b/arbor/memory/gpu_wrappers.cpp
index d037f5b9b732889af9e34c71df6e2a2991a32c08..443bac893f5cee67003f34cf49408bfde8f907bd 100644
--- a/arbor/memory/gpu_wrappers.cpp
+++ b/arbor/memory/gpu_wrappers.cpp
@@ -2,10 +2,11 @@
 #include <string>
 
 #include <arbor/arbexcept.hpp>
+#include <arbor/version.hpp>
 
 #include "util.hpp"
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 
 #include <arbor/gpu/gpu_api.hpp>
 
diff --git a/arbor/util/config.hpp b/arbor/util/config.hpp
index 44273152ef0bbeb5644ad64f23b039447890dc9e..5e8824b74b3d8b2efb1b717556986ebcb9854e09 100644
--- a/arbor/util/config.hpp
+++ b/arbor/util/config.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <arbor/version.hpp>
+
 namespace arb {
 namespace config {
 
@@ -29,7 +31,7 @@ constexpr bool has_power_measurement = true;
 constexpr bool has_power_measurement = false;
 #endif
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 constexpr bool has_gpu = true;
 #else
 constexpr bool has_gpu = false;
diff --git a/arborenv/default_env.cpp b/arborenv/default_env.cpp
index ddeff5fe82b6b6db388d9cc621037bd69130f644..f35b17308db7bbd4957105a04b0b05764b5b8e64 100644
--- a/arborenv/default_env.cpp
+++ b/arborenv/default_env.cpp
@@ -1,11 +1,12 @@
 #include <limits>
 #include <optional>
 
+#include <arbor/version.hpp>
 #include <arborenv/arbenvexcept.hpp>
 #include <arborenv/concurrency.hpp>
 #include <arborenv/default_env.hpp>
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 #include "gpu_api.hpp"
 #endif
 
@@ -29,7 +30,7 @@ ARB_ARBORENV_API unsigned long get_env_num_threads() {
     return *env_val;
 }
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 
 ARB_ARBORENV_API int default_gpu() {
     constexpr const char* env_var = "ARBENV_GPU_ID";
@@ -60,7 +61,7 @@ ARB_ARBORENV_API int default_gpu() {
     return -1;
 }
 
-#endif // def ARB_HAVE_GPU
+#endif // def ARB_GPU_ENABLED
 
 } // namespace arbenv
 
diff --git a/arborenv/private_gpu.cpp b/arborenv/private_gpu.cpp
index ee5cd531d294f70e9914075d67c6f1acc6c83425..edcaef6a459d470ea7901ffdb499d96999dd431b 100644
--- a/arborenv/private_gpu.cpp
+++ b/arborenv/private_gpu.cpp
@@ -5,13 +5,14 @@
 
 #include <mpi.h>
 
+#include <arbor/version.hpp>
 #include <arborenv/arbenvexcept.hpp>
 #include <arborenv/gpu_env.hpp>
 #include "gpu_uuid.hpp"
 
 namespace arbenv {
 
-#ifdef ARB_HAVE_GPU
+#ifdef ARB_GPU_ENABLED
 
 template <>
 ARB_ARBORENV_API int find_private_gpu(MPI_Comm comm) {
@@ -100,7 +101,7 @@ ARB_ARBORENV_API int find_private_gpu(MPI_Comm comm) {
     return -1;
 }
 
-#endif // def ARB_HAVE_GPU
+#endif // def ARB_GPU_ENABLED
 
 } // namespace arbenv
 
diff --git a/doc/dev/mechanism_abi.rst b/doc/dev/mechanism_abi.rst
index a8c9205900f1ca9d98194dfe7f72536657e62316..f077e77296f4f2dfb2ab0bde077ed7132a00140b 100644
--- a/doc/dev/mechanism_abi.rst
+++ b/doc/dev/mechanism_abi.rst
@@ -444,16 +444,36 @@ interface layer <simd>` for more information.
 Making A Loadable Mechanism
 ---------------------------
 
-Mechanisms interface with Arbor by providing three functions, one
-returning the metadata portion, and one for each implemented backend (currently
-two). The latter may return a NULL pointer, indicating that this backend is not
-supported. The naming scheme is shown in the example below
+Mechanisms interface with Arbor by providing a single function, returning
+a structure
+
+.. c:struct:: arb_mechanism
+
+  .. c:member:: arb_get_mechanism_type type
+
+                Pointer to a function ``arb_mechanism_type get_type()``
+
+  .. c:member:: arb_get_mechanism_interface i_cpu
+
+                Pointer to a function ``arb_mechanism_interface get_interface()``
+                that returns a pointer to the CPU interface which may be
+                ``null``.
+
+  .. c:member:: arb_get_mechanism_interface i_gpu
+
+                Pointer to a function ``arb_mechanism_interace get_interface()``
+                that returns a pointer to the GPU interface which may be
+                ``null``.
+
+You can create mechanisms with both ``i_gpu`` and ``i_cpu`` returning ``null``,
+but at least one of the interfaces must be provided or Arbor will refuse to load
+the catalogue this mechanism.
+
+The naming scheme is shown in the example below
 
 .. code:: C
 
-  arb_mechanism_type make_arb_default_catalogue_pas();
-  arb_mechanism_interface* make_arb_default_catalogue_pas_interface_multicore();
-  arb_mechanism_interface* make_arb_default_catalogue_pas_interface_gpu();
+  arb_mechanism make_arb_default_catalogue_pas();
 
 Writing Mechanisms Directly Against the ABI
 -------------------------------------------
diff --git a/mechanisms/BuildModules.cmake b/mechanisms/BuildModules.cmake
index 3aadf9fcb4a8aa6dc6e3e24633390667f386bb33..83b8467cbb81eb5cc0dea673c70683d6111cfa0b 100644
--- a/mechanisms/BuildModules.cmake
+++ b/mechanisms/BuildModules.cmake
@@ -123,11 +123,17 @@ function("make_catalogue")
       list(APPEND catalogue_${MK_CAT_NAME}_source ${MK_CAT_OUT_DIR}/${mech}_gpu.cpp ${MK_CAT_OUT_DIR}/${mech}_gpu.cu)
     endif()
   endforeach()
+
   set(${MK_CAT_OUTPUT} ${catalogue_${MK_CAT_NAME}_source} PARENT_SCOPE)
 
   if(${MK_CAT_STANDALONE})
     add_library(${MK_CAT_NAME}-catalogue SHARED ${catalogue_${MK_CAT_NAME}_source})
     target_compile_definitions(${MK_CAT_NAME}-catalogue PUBLIC STANDALONE=1)
+
+    if(ARB_WITH_GPU)
+      target_compile_definitions(${MK_CAT_NAME}-catalogue PUBLIC ARB_GPU_ENABLED)
+    endif()
+
     target_compile_options(${MK_CAT_NAME}-catalogue PUBLIC ${MK_CAT_CXX_FLAGS_TARGET})
     set_target_properties(${MK_CAT_NAME}-catalogue
       PROPERTIES
diff --git a/mechanisms/generate_catalogue b/mechanisms/generate_catalogue
index 53eec3af393cb2a93e7d3eb43d28bc245c68416e..2a63f7d16e1e84df88d02ec568ec6aec97cc37ce 100755
--- a/mechanisms/generate_catalogue
+++ b/mechanisms/generate_catalogue
@@ -96,19 +96,46 @@ def generate(catalogue, modpfx='', arbpfx='', modules=[], backends=[], namespace
 r'''// Automatically generated by:
 // $cmdline
 
-#include <${arbpfx}mechcat.hpp>
-#include <${arbpfx}mechanism.hpp>
 #include <${arbpfx}mechanism_abi.h>
-$backend_includes
+
 $module_includes
 
-namespace arb {
+#ifdef STANDALONE
+extern "C" {
+    [[gnu::visibility("default")]] const void* get_catalogue(int* n) {
+        static arb_mechanism cat[${n_modules}] = {
+            ${insert_modules}
+        };
+        *n = ${n_modules};
+        return (void*)cat;
+    }
+}
+#else
+
+#include <${arbpfx}mechcat.hpp>
+#include <${arbpfx}mechanism.hpp>
+#include <${arbpfx}assert.hpp>
 
+namespace arb {
 mechanism_catalogue build_${catalogue}_catalogue() {
     mechanism_catalogue cat;
 
+#define ADD(make) do {                                                                  \
+    auto mech = make();                                                                 \
+    auto ty = mech.type();                                                              \
+    auto nm = ty.name;                                                                  \
+    auto ig = mech.i_gpu();                                                             \
+    auto ic = mech.i_cpu();                                                             \
+    arb_assert(ic || ig);                                                               \
+    cat.add(nm, ty);                                                                    \
+    if (ic) cat.register_implementation(nm, std::make_unique<arb::mechanism>(ty, *ic)); \
+    if (ig) cat.register_implementation(nm, std::make_unique<arb::mechanism>(ty, *ig)); \
+} while (false)
+
     $add_modules
-    $register_modules
+
+#undef ADD
+
     return cat;
 }
 
@@ -116,36 +143,22 @@ ARB_ARBOR_API const mechanism_catalogue& global_${catalogue}_catalogue() {
     static mechanism_catalogue cat = build_${catalogue}_catalogue();
     return cat;
 }
-
 } // namespace arb
-
-#ifdef STANDALONE
-extern "C" {
-    [[gnu::visibility("default")]] const void* get_catalogue() {
-        static auto cat = arb::build_${catalogue}_catalogue();
-        return (void*)&cat;
-    }
-}
 #endif
 ''')
 
     def indent(n, lines):
         return '{{:<{0!s}}}'.format(n+1).format('\n').join(lines)
 
-    # TODO: use the commented include list below when private/public
-    # headers are resolved.
-
     return src.safe_substitute(dict(
         cmdline=" ".join(sys.argv),
         arbpfx=arbpfx,
         catalogue=catalogue,
-        backend_includes = indent(0, []),
-        module_includes = indent(0,
+        module_includes=indent(0,
             ['#include "{}{}.hpp"'.format(modpfx, m) for m in modules]),
-        add_modules = indent(4,
-            [f'cat.add("{mod}", make_arb_{catalogue}_catalogue_{mod}());' for mod in modules]),
-        register_modules = indent(4,
-            [f'cat.register_implementation("{mod}", std::make_unique<mechanism>(make_arb_{catalogue}_catalogue_{mod}(), *make_arb_{catalogue}_catalogue_{mod}_interface_{be}()));' for mod in modules for be in backends])
+        add_modules=indent(4, [f'ADD(make_arb_{catalogue}_catalogue_{mod});' for mod in modules]),
+        n_modules=len(modules),
+        insert_modules=indent(12, [f'make_arb_{catalogue}_catalogue_{mod}(),' for mod in modules]),
         ))
 
 
diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp
index 0db24b4990e3a18919dc3ee19945031d57769844..c796b513097ac3ae9b9b3ba001efa0f297efefe8 100644
--- a/modcc/printer/infoprinter.cpp
+++ b/modcc/printer/infoprinter.cpp
@@ -29,15 +29,17 @@ ARB_LIBMODCC_API std::string build_info_header(const Module& m, const printer_op
 
     const auto lowest = std::to_string(std::numeric_limits<double>::lowest());
     const auto max    = std::to_string(std::numeric_limits<double>::max());
+    const auto prefix = std::regex_replace(opt.cpp_namespace, std::regex{"::"}, "_");
+
     out << fmt::format("#pragma once\n\n"
                        "#include <cmath>\n"
-                       "#include <{}mechanism_abi.h>\n\n",
+                       "#include <{0}version.hpp>\n"
+                       "#include <{0}mechanism_abi.h>\n\n",
                        arb_header_prefix());
-
     out << fmt::format("extern \"C\" {{\n"
-                       "  arb_mechanism_type make_{0}_{1}() {{\n"
+                       "  arb_mechanism_type make_{0}_{1}_type() {{\n"
                        "    // Tables\n",
-                       std::regex_replace(opt.cpp_namespace, std::regex{"::"}, "_"),
+                       prefix,
                        name);
 
     const auto& [state_ids, global_ids, param_ids] = public_variable_ids(m);
@@ -108,12 +110,22 @@ ARB_LIBMODCC_API std::string build_info_header(const Module& m, const printer_op
                        module_kind_str(m),
                        m.is_linear(),
                        m.has_post_events())
-        << fmt::format("  arb_mechanism_interface* make_{0}_{1}_interface_multicore(){2}\n"
-                       "  arb_mechanism_interface* make_{0}_{1}_interface_gpu(){3}\n"
-                       "}}\n",
-                       std::regex_replace(opt.cpp_namespace, std::regex{"::"}, "_"),
-                       name,
-                       cpu ? ";" : " { return nullptr; }",
-                       gpu ? ";" : " { return nullptr; }");
+        << fmt::format("  arb_mechanism_interface* make_{0}_{1}_interface_multicore();\n"
+                       "  arb_mechanism_interface* make_{0}_{1}_interface_gpu();\n"
+                       "\n"
+                       "  #ifndef ARB_GPU_ENABLED\n"
+                       "  arb_mechanism_interface* make_{0}_{1}_interface_gpu() {{ return nullptr; }}\n"
+                       "  #endif\n"
+                       "\n"
+                       "  arb_mechanism make_{0}_{1}() {{\n"
+                       "    static arb_mechanism result = {{}};\n"
+                       "    result.type  = make_{0}_{1}_type;\n"
+                       "    result.i_cpu = make_{0}_{1}_interface_multicore;\n"
+                       "    result.i_gpu = make_{0}_{1}_interface_gpu;\n"
+                       "    return result;\n"
+                       "  }}\n"
+                       "}} // extern C\n",
+                       prefix,
+                       name);
     return out.str();
 }
diff --git a/python/example/dynamic-catalogue.py b/python/example/dynamic-catalogue.py
index 773eab0415d400a4d95d194dc105c706e84b0f99..bedd86de81eef64e2e8998a253d42c9e04993cf0 100644
--- a/python/example/dynamic-catalogue.py
+++ b/python/example/dynamic-catalogue.py
@@ -4,7 +4,7 @@ from pathlib import Path
 
 import arbor as arb
 
-cat = 'cat-catalogue.so'
+cat = Path('cat-catalogue.so').resolve()
 
 class recipe(arb.recipe):
     def __init__(self):
@@ -14,7 +14,7 @@ class recipe(arb.recipe):
         self.props = arb.neuron_cable_properties()
         self.props.catalogue = arb.load_catalogue(cat)
         d = arb.decor()
-        d.paint('(all)', 'dummy')
+        d.paint('(all)', arb.density('dummy'))
         d.set_property(Vm=0.0)
         self.cell = arb.cable_cell(self.tree, arb.label_dict(), d)
 
@@ -30,10 +30,10 @@ class recipe(arb.recipe):
     def cell_description(self, gid):
         return self.cell
 
-if not Path(cat).is_file():
+if not cat.is_file():
     print("""Catalogue not found in this directory.
-Please ensure it has been compiled by calling")
-  <arbor>/scripts/build-catalogue cat <arbor>/python/examples/cat
+Please ensure it has been compiled by calling
+  <arbor>/scripts/build-catalogue cat <arbor>/python/example/cat
 where <arbor> is the location of the arbor source tree.""")
     exit(1)
 
diff --git a/python/mechanism.cpp b/python/mechanism.cpp
index a97d035d730c85b07f11ade750f93fd759438669..a5ee801fc40e519eefda3c92bb13728cc98d3127 100644
--- a/python/mechanism.cpp
+++ b/python/mechanism.cpp
@@ -101,7 +101,7 @@ void register_mechanisms(pybind11::module& m) {
             "True if a synapse mechanism has a `POST_EVENT` procedure defined.")
         .def_property_readonly("kind",
                 [](const arb::mechanism_info& info) {
-                    return arb_mechsnism_kind_str(info.kind);
+                    return arb_mechanism_kind_str(info.kind);
                 }, "String representation of the kind of the mechanism.")
         .def("__repr__",
                 [](const arb::mechanism_info& inf) {
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index d7d1954a6a834e21afe130f3a8f56c5e3a0189de..98b5eeed9341e0c95ecc4b48424c196910be49dc 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -241,6 +241,10 @@ if(${CMAKE_POSITION_INDEPENDENT_CODE})
   add_dependencies(unit dummy-catalogue)
 endif()
 
+if(ARB_WITH_GPU)
+  target_compile_definitions(unit PRIVATE ARB_GPU_ENABLED)
+endif()
+
 if(ARB_WITH_NVCC)
     target_compile_options(unit PRIVATE -DARB_CUDA)
 endif()
diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp
index 181c203476bf73868add8ee28d8b3ecabe59ee89..8c863b6100e94fc70808b9794d4cdf09bdea2d7e 100644
--- a/test/unit/test_mechcat.cpp
+++ b/test/unit/test_mechcat.cpp
@@ -289,11 +289,9 @@ TEST(mechcat, loading) {
 #endif
 #else
     EXPECT_THROW(load_catalogue(LIBDIR "/libarbor.a"), bad_catalogue_error);
+    const mechanism_catalogue cat = load_catalogue(LIBDIR "/dummy-catalogue.so");
+    EXPECT_EQ(std::vector<std::string>{"dummy"}, cat.mechanism_names());
 #endif
-    const mechanism_catalogue* cat = nullptr;
-    EXPECT_NO_THROW(cat = &load_catalogue(LIBDIR "/dummy-catalogue.so"));
-    ASSERT_NE(cat, nullptr);
-    EXPECT_EQ(std::vector<std::string>{"dummy"}, cat->mechanism_names());
 }
 #endif
 
diff --git a/test/unit/unit_test_catalogue.cpp b/test/unit/unit_test_catalogue.cpp
index 807ccabb7688127994a130da2b454236176e44f5..a87b0529485640749c025b6ea8d0780835c3f5fe 100644
--- a/test/unit/unit_test_catalogue.cpp
+++ b/test/unit/unit_test_catalogue.cpp
@@ -48,60 +48,58 @@
 
 #include "../gtest.h"
 
-#ifndef ARB_GPU_ENABLED
-#define ADD_MECH(c, x)\
-c.add(#x, make_testing_##x());   \
-c.register_implementation(#x, std::make_unique<arb::mechanism>(make_testing_##x(), *make_testing_##x##_interface_multicore()));
-#else
-#define ADD_MECH(c, x)\
-c.add(#x, make_testing_##x());                                      \
-c.register_implementation(#x, std::make_unique<arb::mechanism>(make_testing_##x(), *make_testing_##x##_interface_multicore())); \
-c.register_implementation(#x, std::make_unique<arb::mechanism>(make_testing_##x(), *make_testing_##x##_interface_gpu()));
-#endif
+#define ADD_MECH(c, x) do {                                             \
+    auto mech = make_testing_##x();                                     \
+    c.add(#x, mech.type());                                             \
+    c.register_implementation(#x, std::make_unique<arb::mechanism>(mech.type(), *mech.i_cpu())); \
+    if (mech.i_gpu()) {                                                 \
+        c.register_implementation(#x, std::make_unique<arb::mechanism>(mech.type(), *mech.i_gpu())); \
+    }                                                                   \
+    } while (false)
 
 using namespace arb;
 
 mechanism_catalogue make_unit_test_catalogue(const mechanism_catalogue& from) {
     mechanism_catalogue cat = from;
 
-    ADD_MECH(cat, gj0)
-    ADD_MECH(cat, gj1)
-    ADD_MECH(cat, test_ca)
-    ADD_MECH(cat, test_kin1)
-    ADD_MECH(cat, test_kinlva)
-    ADD_MECH(cat, ca_linear)
-    ADD_MECH(cat, celsius_test)
-    ADD_MECH(cat, diam_test)
-    ADD_MECH(cat, param_as_state)
-    ADD_MECH(cat, post_events_syn)
-    ADD_MECH(cat, test_linear_state)
-    ADD_MECH(cat, test_linear_init)
-    ADD_MECH(cat, test_linear_init_shuffle)
-    ADD_MECH(cat, test0_kin_diff)
-    ADD_MECH(cat, test0_kin_conserve)
-    ADD_MECH(cat, test0_kin_steadystate)
-    ADD_MECH(cat, test0_kin_compartment)
-    ADD_MECH(cat, test1_kin_diff)
-    ADD_MECH(cat, test1_kin_conserve)
-    ADD_MECH(cat, test2_kin_diff)
-    ADD_MECH(cat, test3_kin_diff)
-    ADD_MECH(cat, test1_kin_steadystate)
-    ADD_MECH(cat, test1_kin_compartment)
-    ADD_MECH(cat, test4_kin_compartment)
-    ADD_MECH(cat, test5_nonlinear_diff)
-    ADD_MECH(cat, test6_nonlinear_diff)
-    ADD_MECH(cat, fixed_ica_current)
-    ADD_MECH(cat, non_linear)
-    ADD_MECH(cat, point_ica_current)
-    ADD_MECH(cat, linear_ca_conc)
-    ADD_MECH(cat, test_cl_valence)
-    ADD_MECH(cat, test_ca_read_valence)
-    ADD_MECH(cat, read_eX)
-    ADD_MECH(cat, write_Xi_Xo)
-    ADD_MECH(cat, write_multiple_eX)
-    ADD_MECH(cat, write_eX)
-    ADD_MECH(cat, read_cai_init)
-    ADD_MECH(cat, write_cai_breakpoint)
+    ADD_MECH(cat, gj0);
+    ADD_MECH(cat, gj1);
+    ADD_MECH(cat, test_ca);
+    ADD_MECH(cat, test_kin1);
+    ADD_MECH(cat, test_kinlva);
+    ADD_MECH(cat, ca_linear);
+    ADD_MECH(cat, celsius_test);
+    ADD_MECH(cat, diam_test);
+    ADD_MECH(cat, param_as_state);
+    ADD_MECH(cat, post_events_syn);
+    ADD_MECH(cat, test_linear_state);
+    ADD_MECH(cat, test_linear_init);
+    ADD_MECH(cat, test_linear_init_shuffle);
+    ADD_MECH(cat, test0_kin_diff);
+    ADD_MECH(cat, test0_kin_conserve);
+    ADD_MECH(cat, test0_kin_steadystate);
+    ADD_MECH(cat, test0_kin_compartment);
+    ADD_MECH(cat, test1_kin_diff);
+    ADD_MECH(cat, test1_kin_conserve);
+    ADD_MECH(cat, test2_kin_diff);
+    ADD_MECH(cat, test3_kin_diff);
+    ADD_MECH(cat, test1_kin_steadystate);
+    ADD_MECH(cat, test1_kin_compartment);
+    ADD_MECH(cat, test4_kin_compartment);
+    ADD_MECH(cat, test5_nonlinear_diff);
+    ADD_MECH(cat, test6_nonlinear_diff);
+    ADD_MECH(cat, fixed_ica_current);
+    ADD_MECH(cat, non_linear);
+    ADD_MECH(cat, point_ica_current);
+    ADD_MECH(cat, linear_ca_conc);
+    ADD_MECH(cat, test_cl_valence);
+    ADD_MECH(cat, test_ca_read_valence);
+    ADD_MECH(cat, read_eX);
+    ADD_MECH(cat, write_Xi_Xo);
+    ADD_MECH(cat, write_multiple_eX);
+    ADD_MECH(cat, write_eX);
+    ADD_MECH(cat, read_cai_init);
+    ADD_MECH(cat, write_cai_breakpoint);
 
     return cat;
 }