diff --git a/CMakeLists.txt b/CMakeLists.txt
index 948430d6bfebd61a3b6dd756e95f567d8c312b5f..3c2e4f73ac2ca95d4d284997b66b0be88f1d038e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -128,6 +128,11 @@ add_subdirectory(aux)
 
 set(arbor_export_dependencies)
 
+# Keep track of which 'components' of arbor are included (this is
+# currently just captures MPI support)
+
+set(arbor_supported_components)
+
 # Target microarchitecture for building arbor libraries, tests and examples
 #---------------------------------------------------------------------------
 if(ARB_ARCH)
@@ -152,7 +157,7 @@ find_package(Threads REQUIRED)
 find_threads_cuda_fix()
 target_link_libraries(arbor-private-deps INTERFACE Threads::Threads)
 
-list(APPEND arbor_export_dependencies "Threads\;REQUIRED")
+list(APPEND arbor_export_dependencies "Threads")
 
 # MPI support
 #-------------------
@@ -173,7 +178,8 @@ if(ARB_WITH_MPI)
     target_link_libraries(arbor-public-deps INTERFACE mpi-wrap)
     install(TARGETS mpi-wrap EXPORT arbor-targets)
 
-    list(APPEND arbor_export_dependencies "MPI\;REQUIRED\;CXX")
+    list(APPEND arbor_export_dependencies "MPI\;COMPONENTS\;CXX")
+    list(APPEND arbor_supported_components "MPI")
 endif()
 
 # CUDA support
@@ -202,7 +208,7 @@ if(Unwind_FOUND)
     target_link_libraries(arbor-private-deps INTERFACE Unwind::unwind)
     target_compile_definitions(arbor-private-deps ARB_WITH_UNWIND)
 
-    list(APPEND arbor_export_dependencies "Unwind\;REQUIRED")
+    list(APPEND arbor_export_dependencies "Unwind")
 endif()
 
 # Build and use modcc unless explicit path given
@@ -313,7 +319,8 @@ write_basic_package_version_file(
     COMPATIBILITY SameMajorVersion)
 
 # Template file will use contents of arbor_export_dependencies to include the
-# required `find_dependency` statements. 
+# required `find_dependency` statements, and arbor_supported_components will
+# be used to check feature support.
 
 configure_file(
     cmake/arbor-config.cmake.in
diff --git a/cmake/arbor-config.cmake.in b/cmake/arbor-config.cmake.in
index 703e70058269727803a00d75ffa474da08799dcd..71448ec5decf116bca0899a6c78af593086b165b 100644
--- a/cmake/arbor-config.cmake.in
+++ b/cmake/arbor-config.cmake.in
@@ -6,4 +6,12 @@ endforeach()
 
 include("${CMAKE_CURRENT_LIST_DIR}/arbor-targets.cmake")
 
+set(_supported_components @arbor_supported_components@)
+
+foreach(component ${arbor_FIND_COMPONENTS})
+    if(NOT "${component}" IN_LIST _supported_components)
+        set(arbor_FOUND FALSE)
+        set(arbor_NOT_FOUND_MESSAGE "Unsupported component: ${component}")
+    endif()
+endforeach()
 
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
index 04797c13124801671cda6e81e047cf1aa65a26b6..9cfab47efaa9771b7eb1b013df93df54d27f3c01 100644
--- a/example/CMakeLists.txt
+++ b/example/CMakeLists.txt
@@ -1,3 +1,7 @@
+# Convenience target that builds all examples.
+# Example executable targets should be added to the 'examples' target as dependencies.
+add_custom_target(examples DEPENDS)
+
 add_subdirectory(miniapp)
 add_subdirectory(dryrun)
 add_subdirectory(generators)
diff --git a/example/bench/CMakeLists.txt b/example/bench/CMakeLists.txt
index 8814dd310a7f8a610ebc46e60e16a6d317bb4218..df585f1149141fbf6c275ea0808e3b46f27fe3e4 100644
--- a/example/bench/CMakeLists.txt
+++ b/example/bench/CMakeLists.txt
@@ -1,3 +1,4 @@
-add_executable(bench bench.cpp recipe.cpp parameters.cpp)
+add_executable(bench EXCLUDE_FROM_ALL bench.cpp recipe.cpp parameters.cpp)
+add_dependencies(examples bench)
 
 target_link_libraries(bench PRIVATE arbor arbor-aux ext-tclap ext-json)
diff --git a/example/brunel/CMakeLists.txt b/example/brunel/CMakeLists.txt
index 657ec23f89b1f758d9fa71f75d2e937dc1c6470f..7614c809b16e61827a6a9f8c8e9fac579f0324d4 100644
--- a/example/brunel/CMakeLists.txt
+++ b/example/brunel/CMakeLists.txt
@@ -1,5 +1,6 @@
-add_executable(brunel-miniapp
+add_executable(brunel-miniapp EXCLUDE_FROM_ALL
     brunel_miniapp.cpp
     io.cpp)
+add_dependencies(examples brunel-miniapp)
 
 target_link_libraries(brunel-miniapp PRIVATE arbor arbor-aux ext-tclap)
diff --git a/example/dryrun/CMakeLists.txt b/example/dryrun/CMakeLists.txt
index f44d6ca72e41a3466283fd94672d1de99acbdee8..45a0745a3a83dd2d3c43522366e09207a3339f4c 100644
--- a/example/dryrun/CMakeLists.txt
+++ b/example/dryrun/CMakeLists.txt
@@ -1,3 +1,4 @@
-add_executable(dryrun dryrun.cpp)
+add_executable(dryrun EXCLUDE_FROM_ALL dryrun.cpp)
+add_dependencies(examples dryrun)
 
 target_link_libraries(dryrun PRIVATE arbor arbor-aux ext-json)
diff --git a/example/generators/CMakeLists.txt b/example/generators/CMakeLists.txt
index bc2a70b58edc2321cd657e12d0cc89c11f677830..df774858376f4e3a0731b507fa38033ecdc37def 100644
--- a/example/generators/CMakeLists.txt
+++ b/example/generators/CMakeLists.txt
@@ -1,3 +1,4 @@
-add_executable(event-gen event_gen.cpp)
+add_executable(event-gen EXCLUDE_FROM_ALL event_gen.cpp)
+add_dependencies(examples event-gen)
 
 target_link_libraries(event-gen PRIVATE arbor arbor-aux ext-json)
diff --git a/example/miniapp/CMakeLists.txt b/example/miniapp/CMakeLists.txt
index 84480623b65fc1db4c1ffe7ef2c88bce718a43dd..8d04d959c74cf6caa2ce7162501b9209b3ed803a 100644
--- a/example/miniapp/CMakeLists.txt
+++ b/example/miniapp/CMakeLists.txt
@@ -1,9 +1,10 @@
-add_executable(miniapp
+add_executable(miniapp EXCLUDE_FROM_ALL
     miniapp.cpp
     io.cpp
     miniapp_recipes.cpp
     morphology_pool.cpp
     trace.cpp
 )
+add_dependencies(examples miniapp)
 
 target_link_libraries(miniapp PRIVATE arbor arbor-aux ext-tclap ext-json)
diff --git a/example/ring/CMakeLists.txt b/example/ring/CMakeLists.txt
index 2fd6c79f86162c50bed0fc1023c8944cad9ac3b9..dc8ff649048ac9726a9f1614aec150c15097c68a 100644
--- a/example/ring/CMakeLists.txt
+++ b/example/ring/CMakeLists.txt
@@ -1,3 +1,4 @@
-add_executable(ring ring.cpp)
+add_executable(ring EXCLUDE_FROM_ALL ring.cpp)
+add_dependencies(examples ring)
 
 target_link_libraries(ring PRIVATE arbor arbor-aux ext-tclap ext-json)
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 85efd242ec6885db00d7c715ddc69612e7baeb84..ef725caab660680f7b503a439d1f29f76f817178 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,21 +1,30 @@
 find_package(Threads REQUIRED)
 find_threads_cuda_fix()
 
-add_library(gtest STATIC gtest-all.cpp)
+add_library(gtest EXCLUDE_FROM_ALL STATIC gtest-all.cpp)
 target_include_directories(gtest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 target_link_libraries(gtest PUBLIC Threads::Threads)
 
+# Convenience target that builds all tests.
+# Test executable targets should be added to the 'tests' target as dependencies.
+add_custom_target(tests)
+
 # Unit tests.
+# Builds: unit.
 add_subdirectory(unit)
 
-# Test validating models, possebly needing other software installed.
+# Model validation.
+# Builds: validate.
 add_subdirectory(validation)
 
 # Test MPI wrappers and distribution operations.
+# Builds: unit-local and unit-mpi (if MPI enabled).
 add_subdirectory(unit-distributed)
 
 # Test modcc internals.
+# Builds: unit-modcc.
 add_subdirectory(unit-modcc)
 
-## Microbenchmarks.
+# Microbenchmarks.
+# Builds: one executable per microbenchmark under ubench/.
 add_subdirectory(ubench)
diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt
index a0fb0040738736606125f3016c110db7b47d8167..21e67ca9f46789d1ebea5843ac796631fcf79db6 100644
--- a/test/ubench/CMakeLists.txt
+++ b/test/ubench/CMakeLists.txt
@@ -23,6 +23,7 @@ endif()
 foreach(bench_src ${bench_sources})
     string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src})
     add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}")
+
     target_link_libraries(${bench_exe} arbor arbor-private-headers ext-benchmark)
     target_compile_options(${bench_exe} PRIVATE ${ARB_CXXOPT_ARCH})
     list(APPEND bench_exe_list ${bench_exe})
diff --git a/test/ubench/mech_vec.cpp b/test/ubench/mech_vec.cpp
index 32e596974c243902c6845ea1a6e0ec7df0e14325..8c0388b856b9f864f2297d5339c28793a31aa202 100644
--- a/test/ubench/mech_vec.cpp
+++ b/test/ubench/mech_vec.cpp
@@ -8,6 +8,7 @@
 
 #include "backends/multicore/fvm.hpp"
 #include "benchmark/benchmark.h"
+#include "execution_context.hpp"
 #include "fvm_lowered_cell_impl.hpp"
 
 using namespace arb;
@@ -208,7 +209,7 @@ void expsyn_1_branch_current(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_expsyn_1_branch, target_handles, probe_handles);
 
     auto& m = find_mechanism("expsyn", cell);
@@ -227,7 +228,7 @@ void expsyn_1_branch_state(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_expsyn_1_branch, target_handles, probe_handles);
 
     auto& m = find_mechanism("expsyn", cell);
@@ -245,7 +246,7 @@ void pas_1_branch_current(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_pas_1_branch, target_handles, probe_handles);
 
     auto& m = find_mechanism("pas", cell);
@@ -263,7 +264,7 @@ void pas_3_branches_current(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_pas_3_branches, target_handles, probe_handles);
 
     auto& m = find_mechanism("pas", cell);
@@ -281,7 +282,7 @@ void hh_1_branch_state(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_hh_1_branch, target_handles, probe_handles);
 
     auto& m = find_mechanism("hh", cell);
@@ -299,7 +300,7 @@ void hh_1_branch_current(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_hh_1_branch, target_handles, probe_handles);
 
     auto& m = find_mechanism("hh", cell);
@@ -317,7 +318,7 @@ void hh_3_branches_state(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_hh_3_branches, target_handles, probe_handles);
 
     auto& m = find_mechanism("hh", cell);
@@ -335,7 +336,7 @@ void hh_3_branches_current(benchmark::State& state) {
     std::vector<target_handle> target_handles;
     probe_association_map<probe_handle> probe_handles;
 
-    fvm_cell cell;
+    fvm_cell cell((execution_context()));
     cell.initialize(gids, rec_hh_3_branches, target_handles, probe_handles);
 
     auto& m = find_mechanism("hh", cell);
diff --git a/test/unit-distributed/CMakeLists.txt b/test/unit-distributed/CMakeLists.txt
index fa7d15f98b71c891a9d6ab2f9daec87d4f849375..a7c60af2809a86bef31e7a7b62252f8e2dc2557d 100644
--- a/test/unit-distributed/CMakeLists.txt
+++ b/test/unit-distributed/CMakeLists.txt
@@ -8,13 +8,17 @@ set(unit-distributed_sources
     test.cpp
 )
 
-add_executable(unit-local ${unit-distributed_sources})
+add_executable(unit-local EXCLUDE_FROM_ALL ${unit-distributed_sources})
+add_dependencies(tests unit-local)
+
 target_compile_options(unit-local PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(unit-local PRIVATE TEST_LOCAL)
 target_link_libraries(unit-local PRIVATE gtest arbor arbor-aux arbor-private-headers)
 
 if(ARB_WITH_MPI)
-    add_executable(unit-mpi ${unit-distributed_sources})
+    add_executable(unit-mpi EXCLUDE_FROM_ALL ${unit-distributed_sources})
+    add_dependencies(tests unit-mpi)
+
     target_compile_options(unit-mpi PRIVATE ${ARB_CXXOPT_ARCH})
     target_compile_definitions(unit-mpi PRIVATE TEST_MPI)
     target_link_libraries(unit-mpi PRIVATE gtest arbor arbor-aux arbor-private-headers)
diff --git a/test/unit-modcc/CMakeLists.txt b/test/unit-modcc/CMakeLists.txt
index 813f4a911605e4c75dcd2b027f8db21b27c0b88c..813c03ff32ae7b6ca6c32a7bc0b0a8097a1d4410 100644
--- a/test/unit-modcc/CMakeLists.txt
+++ b/test/unit-modcc/CMakeLists.txt
@@ -21,7 +21,10 @@ set(unit-modcc_sources
     expr_expand.cpp
 )
 
-add_executable(unit-modcc ${unit-modcc_sources})
+add_executable(unit-modcc EXCLUDE_FROM_ALL ${unit-modcc_sources})
+if(NOT ARB_WITH_EXTERNAL_MODCC)
+    add_dependencies(tests unit-modcc)
+endif()
+
 target_link_libraries(unit-modcc PRIVATE libmodcc gtest)
 target_compile_definitions(unit-modcc PRIVATE "DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"")
-set_target_properties(unit-modcc PROPERTIES EXCLUDE_FROM_ALL ${ARB_WITH_EXTERNAL_MODCC})
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 4cacc7cf9ef1f0576f4b5982e220adf76d037dc7..103095c22ef04813373c9bde16cda95418b254cb 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -138,8 +138,10 @@ if(ARB_WITH_CUDA)
     )
 endif()
 
-add_executable(unit ${unit_sources} ${test_mech_sources})
+add_executable(unit EXCLUDE_FROM_ALL ${unit_sources} ${test_mech_sources})
 add_dependencies(unit build_test_mods)
+add_dependencies(tests unit)
+
 target_compile_options(unit PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/swc\"")
 target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
diff --git a/test/validation/CMakeLists.txt b/test/validation/CMakeLists.txt
index 6257a38dccbf35aef0329bb1eb21523ed4c1d9b2..9e975a67bb82c2a06f880516aa36ebc900efd17f 100644
--- a/test/validation/CMakeLists.txt
+++ b/test/validation/CMakeLists.txt
@@ -13,7 +13,9 @@ set(validation_sources
     validate.cpp
 )
 
-add_executable(validate ${validation_sources})
+add_executable(validate EXCLUDE_FROM_ALL ${validation_sources})
+add_dependencies(tests validate)
+
 target_compile_options(validate PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(validate PRIVATE "ARB_DATADIR=\"${ARB_VALIDATION_DATA_DIR}\"")
 target_link_libraries(validate PRIVATE gtest arbor arbor-aux ext-json)