diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index afc316548f5..eb1734b2e5d 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -117,8 +117,10 @@ jobs: - name: Downstream build run: cmake --build build-downstream - - name: Downstream run - run: ./build-downstream/bin/ShowActsVersion + - name: Downstream tests + run: > + CI/dependencies/run.sh .env + ctest --test-dir build-downstream --output-on-failure linux_examples_test: runs-on: ubuntu-latest @@ -318,8 +320,8 @@ jobs: - name: Downstream build run: cmake --build build-downstream - - name: Downstream run - run: ./build-downstream/bin/ShowActsVersion + - name: Downstream tests + run: ctest --test-dir build-downstream --output-on-failure macos: runs-on: macos-26 @@ -406,8 +408,8 @@ jobs: - name: Downstream build run: cmake --build build-downstream - - name: Downstream run - run: ./build-downstream/bin/ShowActsVersion + - name: Downstream tests + run: ctest --test-dir build-downstream --output-on-failure external_eic-shell: runs-on: ubuntu-latest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 89924b65b97..ac4b86e3b3d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -253,8 +253,8 @@ build_linux_ubuntu: # Downstream build - cmake --build build-downstream - # Downstream run - - ./build-downstream/bin/ShowActsVersion + # Downstream tests + - ctest --test-dir build-downstream --output-on-failure after_script: - !reference [.spack_cleanup, after_script] @@ -369,8 +369,8 @@ linux_physmon: # Downstream build - cmake --build build-downstream - # Downstream run - - ./build-downstream/bin/ShowActsVersion + # Downstream tests + - ctest --test-dir build-downstream --output-on-failure after_script: - !reference [.spack_cleanup, after_script] diff --git a/CI/check_unused_files.py b/CI/check_unused_files.py index 9ae2db1f781..3fab7724a00 100755 --- a/CI/check_unused_files.py +++ b/CI/check_unused_files.py @@ -75,6 +75,8 @@ def main(): "Core/include/Acts/EventData/detail/ParameterTraits.hpp", "Core/include/Acts/Seeding/PathSeeder.hpp", "Tests/CommonHelpers/include/ActsTests/CommonHelpers/TestSpacePoint.hpp", + "GeometryModule.h", + "runtime_geometry_modules.md", ) suffix_header = ( diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 0985bbe93e9..20473bdadac 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -10,6 +10,16 @@ target_sources(ActsCore PRIVATE src/ActsVersion.cpp) target_compile_features(ActsCore PUBLIC ${ACTS_CXX_STANDARD_FEATURE}) +set(ACTS_GEOMETRY_MODULE_CAPI 1) +set(_acts_geometry_module_abi_tag + "acts-${Acts_VERSION}|sys-${CMAKE_SYSTEM_NAME}|arch-${CMAKE_SYSTEM_PROCESSOR}|cxx-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}|capi-${ACTS_GEOMETRY_MODULE_CAPI}" +) +set(ACTS_GEOMETRY_MODULE_ABI_TAG + "${_acts_geometry_module_abi_tag}" + CACHE INTERNAL + "ACTS geometry module ABI tag" +) + target_include_directories( ActsCore PUBLIC @@ -19,6 +29,14 @@ target_include_directories( $ ) target_link_libraries(ActsCore PUBLIC Boost::boost Eigen3::Eigen) +if(CMAKE_DL_LIBS) + target_link_libraries(ActsCore PRIVATE ${CMAKE_DL_LIBS}) +endif() + +target_compile_definitions( + ActsCore + PRIVATE ACTS_GEOMETRY_MODULE_ABI_TAG="${_acts_geometry_module_abi_tag}" +) if(ACTS_PARAMETER_DEFINITIONS_HEADER) target_compile_definitions( diff --git a/Core/include/Acts/Geometry/GeometryModule.h b/Core/include/Acts/Geometry/GeometryModule.h new file mode 100644 index 00000000000..c4e0a1fc5f9 --- /dev/null +++ b/Core/include/Acts/Geometry/GeometryModule.h @@ -0,0 +1,18 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +struct ActsGeometryModuleV1 { + const char* module_abi_tag; + const char* user_data_type; + void* (*build)(const void* user_data, const void* logger); + void (*destroy)(void* handle); +}; + +extern "C" const ActsGeometryModuleV1* acts_geometry_module_v1(void); diff --git a/Core/include/Acts/Geometry/GeometryModuleHelper.hpp b/Core/include/Acts/Geometry/GeometryModuleHelper.hpp new file mode 100644 index 00000000000..36a94599f0f --- /dev/null +++ b/Core/include/Acts/Geometry/GeometryModuleHelper.hpp @@ -0,0 +1,45 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Geometry/GeometryModule.h" +#include "Acts/Geometry/TrackingGeometry.hpp" + +#include + +namespace Acts::detail { +using BuildFunction = std::unique_ptr (*)(const Logger&); +const ActsGeometryModuleV1* getGeometryModule(const char* module_abi_tag, + const char* user_data_type, + BuildFunction buildFunc); +// Low-level shared helper: accepts a raw build function pointer matching the +// C ABI struct's .build field. Handles static struct init and destroy. +const ActsGeometryModuleV1* getGeometryModuleFromRaw( + const char* module_abi_tag, const char* user_data_type, + void* (*buildFunc)(const void*, const void*)); +} // namespace Acts::detail + +// Internal — do not use directly. +#define ACTS_IMPL_GEOMETRY_MODULE_ENTRY(get_module_expr) \ + extern "C" const ActsGeometryModuleV1* acts_geometry_module_v1(void) { \ + return (get_module_expr); \ + } + +// Emit a clear error only when the macro is actually expanded without the tag, +// rather than at include time — Acts internal code includes this header too. +#ifdef ACTS_GEOMETRY_MODULE_ABI_TAG +#define ACTS_DEFINE_GEOMETRY_MODULE(build_function) \ + ACTS_IMPL_GEOMETRY_MODULE_ENTRY(Acts::detail::getGeometryModule( \ + ACTS_GEOMETRY_MODULE_ABI_TAG, nullptr, (build_function))) +#else +#define ACTS_DEFINE_GEOMETRY_MODULE(build_function) \ + static_assert(false, \ + "ACTS_GEOMETRY_MODULE_ABI_TAG must be provided via " \ + "CMake (use acts_add_geometry_module).") +#endif diff --git a/Core/include/Acts/Geometry/GeometryModuleLoader.hpp b/Core/include/Acts/Geometry/GeometryModuleLoader.hpp new file mode 100644 index 00000000000..bc43bd69be0 --- /dev/null +++ b/Core/include/Acts/Geometry/GeometryModuleLoader.hpp @@ -0,0 +1,47 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#if !defined(__unix__) && !defined(__APPLE__) +#error \ + "Runtime geometry modules are only supported on Unix-like systems (Linux, macOS)." +#endif + +#include "Acts/Utilities/Logger.hpp" + +#include +#include + +namespace Acts { + +class TrackingGeometry; + +/// Load a module shared library, validate ABI compatibility, build and return +/// the tracking geometry. The returned deleter keeps the module loaded until +/// the geometry is destroyed. Throws if the module requires user data (e.g. +/// a DD4hep module) — use the appropriate typed loader instead. +/// @param modulePath Path to the geometry module shared library. +/// @param logger Logger instance used by the module loader. +/// @return Shared pointer to the loaded tracking geometry. +std::shared_ptr loadGeometryModule( + const std::filesystem::path& modulePath, + const Logger& logger = getDummyLogger()); + +namespace detail { +/// Low-level loader used by typed wrappers (e.g. loadDD4hepGeometryModule). +/// Validates that the module's ABI tag matches \a expectedAbiTag. +/// Validates that the module's user_data_type matches \a expectedUserDataType +/// (nullptr means the module must declare no user data requirement). +std::shared_ptr loadGeometryModuleImpl( + const std::filesystem::path& modulePath, const char* expectedAbiTag, + const char* expectedUserDataType, const void* userData, + const Logger& logger); +} // namespace detail + +} // namespace Acts diff --git a/Core/src/Geometry/CMakeLists.txt b/Core/src/Geometry/CMakeLists.txt index 753bf9a0093..0e3b5da66e3 100644 --- a/Core/src/Geometry/CMakeLists.txt +++ b/Core/src/Geometry/CMakeLists.txt @@ -16,6 +16,8 @@ target_sources( GenericApproachDescriptor.cpp GenericCuboidVolumeBounds.cpp GeometryIdentifier.cpp + GeometryModuleHelper.cpp + GeometryModuleLoader.cpp GlueVolumesDescriptor.cpp Layer.cpp LayerArrayCreator.cpp diff --git a/Core/src/Geometry/GeometryModuleHelper.cpp b/Core/src/Geometry/GeometryModuleHelper.cpp new file mode 100644 index 00000000000..3bcc1d0fa3d --- /dev/null +++ b/Core/src/Geometry/GeometryModuleHelper.cpp @@ -0,0 +1,64 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/GeometryModuleHelper.hpp" + +#include "Acts/Geometry/GeometryModule.h" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include + +namespace Acts::detail { + +const ActsGeometryModuleV1* getGeometryModuleFromRaw( + const char* module_abi_tag, const char* user_data_type, + void* (*buildFunc)(const void*, const void*)) { + static const ActsGeometryModuleV1 s_module = { + .module_abi_tag = module_abi_tag, + .user_data_type = user_data_type, + .build = buildFunc, + .destroy = + [](void* handle) noexcept { + if (handle == nullptr) { + return; + } + + delete static_cast(handle); + }, + }; + + return &s_module; +} + +const ActsGeometryModuleV1* getGeometryModule(const char* module_abi_tag, + const char* user_data_type, + BuildFunction buildFunc) { + static BuildFunction s_buildFunc = buildFunc; + + return getGeometryModuleFromRaw( + module_abi_tag, user_data_type, + [](const void* /*userData*/, const void* loggerPtr) noexcept -> void* { + if (loggerPtr == nullptr) { + return nullptr; + } + const auto& logger = *static_cast(loggerPtr); + try { + return s_buildFunc(logger).release(); + } catch (const std::exception& e) { + ACTS_ERROR("Failed to build geometry module: " << e.what()); + return nullptr; + } catch (...) { + ACTS_ERROR("Failed to build geometry module"); + return nullptr; + } + }); +} + +} // namespace Acts::detail diff --git a/Core/src/Geometry/GeometryModuleLoader.cpp b/Core/src/Geometry/GeometryModuleLoader.cpp new file mode 100644 index 00000000000..e700f99dd67 --- /dev/null +++ b/Core/src/Geometry/GeometryModuleLoader.cpp @@ -0,0 +1,143 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/GeometryModuleLoader.hpp" + +#include "Acts/Geometry/GeometryModule.h" +#include "Acts/Geometry/TrackingGeometry.hpp" + +#include +#include +#include + +#include + +#ifndef ACTS_GEOMETRY_MODULE_ABI_TAG +#error \ + "ACTS_GEOMETRY_MODULE_ABI_TAG must be provided by CMake when building ActsCore." +#endif + +namespace { + +using GeometryModuleEntryPointV1 = const ActsGeometryModuleV1* (*)(void); + +std::shared_ptr openSharedLibrary(const std::filesystem::path& path) { + if (!std::filesystem::exists(path)) { + throw std::runtime_error( + std::format("Geometry module file does not exist: {}", path.string())); + } + + void* rawHandle = ::dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (rawHandle == nullptr) { + const char* error = ::dlerror(); + throw std::runtime_error(std::format( + "Failed to load geometry module '{}': {}", path.string(), error)); + } + return std::shared_ptr(rawHandle, [](void* handle) { + if (handle != nullptr) { + ::dlclose(handle); + } + }); +} + +GeometryModuleEntryPointV1 resolveEntrypointV1( + const std::filesystem::path& path, const std::shared_ptr& library) { + ::dlerror(); + void* symbol = ::dlsym(library.get(), "acts_geometry_module_v1"); + if (const char* error = ::dlerror(); error != nullptr) { + throw std::runtime_error( + std::format("Failed to resolve acts_geometry_module_v1 in '{}': {}", + path.string(), error)); + } + if (symbol == nullptr) { + throw std::runtime_error(std::format( + "Entry point acts_geometry_module_v1 resolved to nullptr in '{}'", + path.string())); + } + return reinterpret_cast(symbol); +} + +const char* geometryModuleHostAbiTag() noexcept { + return ACTS_GEOMETRY_MODULE_ABI_TAG; +} + +} // namespace + +namespace Acts::detail { + +std::shared_ptr loadGeometryModuleImpl( + const std::filesystem::path& modulePath, const char* expectedAbiTag, + const char* expectedUserDataType, const void* userData, + const Logger& logger) { + auto library = openSharedLibrary(modulePath); + auto entryPoint = resolveEntrypointV1(modulePath, library); + const ActsGeometryModuleV1* descriptor = entryPoint(); + if (descriptor == nullptr) { + throw std::runtime_error("Geometry module descriptor is null"); + } + if (descriptor->module_abi_tag == nullptr || descriptor->build == nullptr || + descriptor->destroy == nullptr) { + throw std::runtime_error("Geometry module descriptor is incomplete"); + } + if (expectedAbiTag == nullptr) { + throw std::runtime_error("Expected geometry module ABI tag is null"); + } + if (std::strcmp(descriptor->module_abi_tag, expectedAbiTag) != 0) { + throw std::runtime_error(std::format( + "Geometry module ABI mismatch: module='{}' host='{}' path='{}'", + descriptor->module_abi_tag, expectedAbiTag, modulePath.string())); + } + + // Validate user_data_type: both sides must agree on whether userData is + // needed and what type it is. + if (const char* actualType = descriptor->user_data_type; + !((expectedUserDataType == nullptr && actualType == nullptr) || + (expectedUserDataType != nullptr && actualType != nullptr && + std::strcmp(actualType, expectedUserDataType) == 0))) { + if (actualType == nullptr) { + throw std::runtime_error(std::format( + "Geometry module '{}' does not require user data; " + "use loadGeometryModule(path, logger) without extra context", + modulePath.string())); + } else if (expectedUserDataType == nullptr) { + throw std::runtime_error(std::format( + "Geometry module '{}' requires user data of type '{}'; " + "use the appropriate typed loader (e.g. loadDD4hepGeometryModule)", + modulePath.string(), actualType)); + } else { + throw std::runtime_error( + std::format("Geometry module '{}' user_data_type mismatch: " + "expected '{}', module declares '{}'", + modulePath.string(), expectedUserDataType, actualType)); + } + } + + void* rawHandle = descriptor->build(userData, &logger); + if (rawHandle == nullptr) { + throw std::runtime_error("Geometry module build returned null handle"); + } + + auto destroyFn = descriptor->destroy; + return std::shared_ptr( + static_cast(rawHandle), + [destroyFn, library = std::move(library)](TrackingGeometry* geometry) { + destroyFn(static_cast(geometry)); + }); +} + +} // namespace Acts::detail + +namespace Acts { + +std::shared_ptr loadGeometryModule( + const std::filesystem::path& modulePath, const Logger& logger) { + return detail::loadGeometryModuleImpl(modulePath, geometryModuleHostAbiTag(), + nullptr, nullptr, logger); +} + +} // namespace Acts diff --git a/Plugins/DD4hep/CMakeLists.txt b/Plugins/DD4hep/CMakeLists.txt index 3f4922fe8ce..24c93ca6163 100644 --- a/Plugins/DD4hep/CMakeLists.txt +++ b/Plugins/DD4hep/CMakeLists.txt @@ -1,5 +1,7 @@ acts_add_library( PluginDD4hep + src/GeometryModuleHelper.cpp + src/GeometryModuleLoader.cpp src/ConvertDD4hepDetector.cpp src/DD4hepBinningHelpers.cpp src/DD4hepMaterialHelpers.cpp @@ -11,6 +13,10 @@ acts_add_library( ACTS_INCLUDE_FOLDER include/ActsPlugins ) +set(_acts_dd4hep_geometry_module_abi_tag + "${ACTS_GEOMETRY_MODULE_ABI_TAG}|dd4hep-${DD4hep_VERSION}" +) + target_include_directories( ActsPluginDD4hep PUBLIC @@ -19,6 +25,11 @@ target_include_directories( ) target_link_libraries(ActsPluginDD4hep PUBLIC Acts::PluginRoot) +target_compile_definitions( + ActsPluginDD4hep + PRIVATE + ACTS_DD4HEP_GEOMETRY_MODULE_ABI_TAG="${_acts_dd4hep_geometry_module_abi_tag}" +) acts_compile_headers(PluginDD4hep GLOB include/**/*.hpp) diff --git a/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleHelper.hpp b/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleHelper.hpp new file mode 100644 index 00000000000..651df15b5ba --- /dev/null +++ b/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleHelper.hpp @@ -0,0 +1,38 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Geometry/GeometryModuleHelper.hpp" + +namespace dd4hep { +class Detector; +} + +namespace Acts { +class TrackingGeometry; +} + +namespace ActsPlugins::DD4hep::detail { +using BuildFunction = std::unique_ptr (*)( + const dd4hep::Detector&, const Acts::Logger&); +const ActsGeometryModuleV1* getGeometryModule(const char* module_abi_tag, + BuildFunction buildFunc); +} // namespace ActsPlugins::DD4hep::detail + +#ifdef ACTS_GEOMETRY_MODULE_ABI_TAG +#define ACTS_DEFINE_DD4HEP_GEOMETRY_MODULE(build_function) \ + ACTS_IMPL_GEOMETRY_MODULE_ENTRY( \ + ActsPlugins::DD4hep::detail::getGeometryModule( \ + ACTS_GEOMETRY_MODULE_ABI_TAG, (build_function))) +#else +#define ACTS_DEFINE_DD4HEP_GEOMETRY_MODULE(build_function) \ + static_assert(false, \ + "ACTS_GEOMETRY_MODULE_ABI_TAG must be provided via " \ + "CMake (use acts_add_dd4hep_geometry_module).") +#endif diff --git a/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleLoader.hpp b/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleLoader.hpp new file mode 100644 index 00000000000..f33b745c40d --- /dev/null +++ b/Plugins/DD4hep/include/ActsPlugins/DD4hep/GeometryModuleLoader.hpp @@ -0,0 +1,41 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#if !defined(__unix__) && !defined(__APPLE__) +#error \ + "Runtime geometry modules are only supported on Unix-like systems (Linux, macOS)." +#endif + +#include "Acts/Utilities/Logger.hpp" + +#include +#include + +namespace dd4hep { +class Detector; +} + +namespace Acts { + +class TrackingGeometry; + +/// Load a DD4hep geometry module shared library with the given detector, +/// validate ABI compatibility, build and return the tracking geometry. +/// The returned deleter keeps the module loaded until the geometry is +/// destroyed. +/// @param modulePath Path to the geometry module shared library. +/// @param detector DD4hep detector instance passed to the module. +/// @param logger Logger instance used by the module loader. +/// @return Shared pointer to the loaded tracking geometry. +std::shared_ptr loadDD4hepGeometryModule( + const std::filesystem::path& modulePath, const dd4hep::Detector& detector, + const Logger& logger = getDummyLogger()); + +} // namespace Acts diff --git a/Plugins/DD4hep/src/GeometryModuleHelper.cpp b/Plugins/DD4hep/src/GeometryModuleHelper.cpp new file mode 100644 index 00000000000..8466235463a --- /dev/null +++ b/Plugins/DD4hep/src/GeometryModuleHelper.cpp @@ -0,0 +1,45 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "ActsPlugins/DD4hep/GeometryModuleHelper.hpp" + +#include "Acts/Utilities/Logger.hpp" + +#include + +namespace ActsPlugins::DD4hep::detail { + +const ActsGeometryModuleV1* getGeometryModule(const char* module_abi_tag, + BuildFunction buildFunc) { + static BuildFunction s_buildFunc = buildFunc; + + return Acts::detail::getGeometryModuleFromRaw( + module_abi_tag, "dd4hep::Detector", + [](const void* userData, const void* loggerPtr) noexcept -> void* { + if (loggerPtr == nullptr) { + return nullptr; + } + const auto& logger = *static_cast(loggerPtr); + try { + if (userData == nullptr) { + throw std::invalid_argument("DD4hep detector is null"); + } + const auto& detector = + *static_cast(userData); + return s_buildFunc(detector, logger).release(); + } catch (const std::exception& e) { + ACTS_ERROR("Failed to build geometry module: " << e.what()); + return nullptr; + } catch (...) { + ACTS_ERROR("Failed to build geometry module"); + return nullptr; + } + }); +} + +} // namespace ActsPlugins::DD4hep::detail diff --git a/Plugins/DD4hep/src/GeometryModuleLoader.cpp b/Plugins/DD4hep/src/GeometryModuleLoader.cpp new file mode 100644 index 00000000000..b7b7cf839a6 --- /dev/null +++ b/Plugins/DD4hep/src/GeometryModuleLoader.cpp @@ -0,0 +1,30 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "ActsPlugins/DD4hep/GeometryModuleLoader.hpp" + +#include "Acts/Geometry/GeometryModuleLoader.hpp" + +#include + +#ifndef ACTS_DD4HEP_GEOMETRY_MODULE_ABI_TAG +#error \ + "ACTS_DD4HEP_GEOMETRY_MODULE_ABI_TAG must be provided by CMake when building ActsPluginDD4hep." +#endif + +namespace Acts { + +std::shared_ptr loadDD4hepGeometryModule( + const std::filesystem::path& modulePath, const dd4hep::Detector& detector, + const Logger& logger) { + return ::Acts::detail::loadGeometryModuleImpl( + modulePath, ACTS_DD4HEP_GEOMETRY_MODULE_ABI_TAG, "dd4hep::Detector", + &detector, logger); +} + +} // namespace Acts diff --git a/Tests/DownstreamProject/CMakeLists.txt b/Tests/DownstreamProject/CMakeLists.txt index 08cdf5cb34d..0adbb361ce3 100644 --- a/Tests/DownstreamProject/CMakeLists.txt +++ b/Tests/DownstreamProject/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.11) project(ActsDownstreamProject) +enable_testing() # find all optional components that are build find_package( @@ -52,3 +53,33 @@ if(GEOMODEL) find_package(Acts CONFIG REQUIRED COMPONENTS PluginGeoModel) target_link_libraries(ShowActsVersion PRIVATE Acts::PluginGeoModel) endif() + +find_package(Boost REQUIRED COMPONENTS program_options) + +include(ActsGeometryModuleHelpers) + +acts_add_geometry_module(SimpleGeometryModule GeometryModule.cpp) + +add_executable(LoadGeometryModule LoadGeometryModule.cpp) +target_link_libraries( + LoadGeometryModule + PRIVATE Acts::Core Boost::program_options +) + +add_test(NAME DownstreamShowActsVersion COMMAND ShowActsVersion) +add_test( + NAME DownstreamLoadGeometryModule + COMMAND LoadGeometryModule $ +) + +if(DD4HEP) + include(ActsDD4hepGeometryModuleHelpers) + + acts_add_dd4hep_geometry_module( + DD4hepGeometryModule + DD4hepGeometryModule.cpp + ) + + target_link_libraries(LoadGeometryModule PRIVATE Acts::PluginDD4hep) + target_compile_definitions(LoadGeometryModule PRIVATE ACTS_HAVE_DD4HEP) +endif() diff --git a/Tests/DownstreamProject/DD4hepGeometryModule.cpp b/Tests/DownstreamProject/DD4hepGeometryModule.cpp new file mode 100644 index 00000000000..4e872084e15 --- /dev/null +++ b/Tests/DownstreamProject/DD4hepGeometryModule.cpp @@ -0,0 +1,28 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsPlugins/DD4hep/GeometryModuleHelper.hpp" + +#include +#include + +#include + +namespace { + +std::unique_ptr buildDD4hepGeometryModule( + const dd4hep::Detector& /*detector*/, const Acts::Logger& logger) { + ACTS_ERROR("DD4hep geometry module stub - not implemented"); + throw std::runtime_error("DD4hep geometry module stub - not implemented"); +} + +} // namespace + +ACTS_DEFINE_DD4HEP_GEOMETRY_MODULE(buildDD4hepGeometryModule) diff --git a/Tests/DownstreamProject/GeometryModule.cpp b/Tests/DownstreamProject/GeometryModule.cpp new file mode 100644 index 00000000000..b32f8344a1b --- /dev/null +++ b/Tests/DownstreamProject/GeometryModule.cpp @@ -0,0 +1,57 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/Blueprint.hpp" +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/GeometryModuleHelper.hpp" +#include "Acts/Geometry/StaticBlueprintNode.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" + +#include +#include + +namespace { + +std::unique_ptr buildGeometryModule( + const Acts::Logger& logger) { + using namespace Acts; + + ACTS_INFO("Building geometry module"); + + const auto gctx = GeometryContext::dangerouslyDefaultConstruct(); + + Experimental::Blueprint::Config cfg; + cfg.envelope = ExtentEnvelope{{ + .x = {10., 10.}, + .y = {10., 10.}, + .z = {10., 10.}, + }}; + + Experimental::Blueprint root{cfg}; + + auto outerBounds = std::make_shared(1000., 1000., 1000.); + auto outerVol = std::make_unique(Transform3::Identity(), + outerBounds, "Outer"); + auto outerNode = + std::make_shared(std::move(outerVol)); + root.addChild(outerNode); + + auto trackingGeometry = root.construct({}, gctx, *logger.clone("Geometry")); + if (trackingGeometry == nullptr) { + throw std::runtime_error( + "Failed to build Gen3 tracking geometry in downstream module"); + } + + return trackingGeometry; +} + +} // namespace + +ACTS_DEFINE_GEOMETRY_MODULE(buildGeometryModule) diff --git a/Tests/DownstreamProject/LoadGeometryModule.cpp b/Tests/DownstreamProject/LoadGeometryModule.cpp new file mode 100644 index 00000000000..1131e228243 --- /dev/null +++ b/Tests/DownstreamProject/LoadGeometryModule.cpp @@ -0,0 +1,94 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/GeometryModuleLoader.hpp" + +#ifdef ACTS_HAVE_DD4HEP +#include "ActsPlugins/DD4hep/GeometryModuleLoader.hpp" + +#include +#endif + +#include +#include +#include + +#include + +namespace po = boost::program_options; + +int main(int argc, char* argv[]) { + std::string modulePath; +#ifdef ACTS_HAVE_DD4HEP + std::string dd4hepXml; +#endif + + po::options_description desc("Options"); + // clang-format off + desc.add_options() + ("help,h", "Show help") + ("module", po::value(&modulePath)->required(), + "Path to geometry module shared library") +#ifdef ACTS_HAVE_DD4HEP + ("dd4hep", po::value(&dd4hepXml), + "Path to DD4hep compact XML file; activates DD4hep detector loading") +#endif + ; + // clang-format on + + po::positional_options_description pos; + pos.add("module", 1); + + po::variables_map vm; + try { + po::store( + po::command_line_parser(argc, argv).options(desc).positional(pos).run(), + vm); + if (vm.count("help") != 0u) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + po::notify(vm); + } catch (const po::error& e) { + std::cerr << "Error: " << e.what() << "\n" << desc << std::endl; + return EXIT_FAILURE; + } + + try { + auto logger = + Acts::getDefaultLogger("LoadGeometryModule", Acts::Logging::VERBOSE); + + std::shared_ptr trackingGeometry; + +#ifdef ACTS_HAVE_DD4HEP + if (vm.count("dd4hep") != 0u) { + auto detector = dd4hep::Detector::make_unique("LoadGeometryModule"); + detector->fromCompact(dd4hepXml); + detector->volumeManager(); + detector->apply("DD4hepVolumeManager", 0, nullptr); + trackingGeometry = + Acts::loadDD4hepGeometryModule(modulePath, *detector, *logger); + } else +#endif + { + trackingGeometry = Acts::loadGeometryModule(modulePath, *logger); + } + + std::cout << "Geometry module loaded successfully" << std::endl; + if (!trackingGeometry) { + std::cerr << "Geometry module returned an invalid handle" << std::endl; + return EXIT_FAILURE; + } + } catch (const std::exception& e) { + std::cerr << "Unexpected failure while loading geometry module: " + << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/Tests/DownstreamProject/README.md b/Tests/DownstreamProject/README.md index 0e1eec86f1e..794ef81079a 100644 --- a/Tests/DownstreamProject/README.md +++ b/Tests/DownstreamProject/README.md @@ -3,6 +3,6 @@ This project demonstrates how to use ACTS in a downstream project. ACTS is used as an external package and is expected to be installed by the user. The project is intentionally bare-bones. It only shows how to set up the CMake configuration -but does not implement any functionality. +and includes a smoke test for the runtime geometry module C API. **Note** This directory is not included in the regular build. diff --git a/cmake/ActsConfig.cmake.in b/cmake/ActsConfig.cmake.in index b09a140591b..88ca4ffa3c5 100644 --- a/cmake/ActsConfig.cmake.in +++ b/cmake/ActsConfig.cmake.in @@ -16,6 +16,7 @@ set(Acts_COMPONENTS @_components@) set(Acts_COMMIT_HASH "@_acts_commit_hash@") set(Acts_COMMIT_HASH_SHORT "@_acts_commit_hash_short@") +set(ACTS_GEOMETRY_MODULE_ABI_TAG "@ACTS_GEOMETRY_MODULE_ABI_TAG@") # print version and components information if(NOT Acts_FIND_QUIETLY) @@ -46,8 +47,13 @@ foreach(_component ${Acts_FIND_COMPONENTS}) endforeach() # add this to the current CMAKE_MODULE_PATH to find third party modules -# that not provide a XXXConfig.cmake or XXX-config.cmake file -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Modules) +# that not provide a XXXConfig.cmake or XXX-config.cmake file, and to allow +# including Acts-provided cmake helpers (e.g. ActsDD4hepGeometryModuleHelpers) +list( + APPEND CMAKE_MODULE_PATH + ${CMAKE_CURRENT_LIST_DIR}/Modules + ${CMAKE_CURRENT_LIST_DIR} +) # find external dependencies that are needed to link with Acts. since the # exported Acts targets only store the linked external target names they need diff --git a/cmake/ActsCreatePackageConfig.cmake b/cmake/ActsCreatePackageConfig.cmake index 3e21a69cd8b..b576a8b63d0 100644 --- a/cmake/ActsCreatePackageConfig.cmake +++ b/cmake/ActsCreatePackageConfig.cmake @@ -17,6 +17,16 @@ configure_package_config_file( INSTALL_DESTINATION ${install_package_config_dir} PATH_VARS CMAKE_INSTALL_BINDIR CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR ) +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/ActsGeometryModuleHelpers.cmake + ${PROJECT_BINARY_DIR}/ActsGeometryModuleHelpers.cmake + COPYONLY +) +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/ActsDD4hepGeometryModuleHelpers.cmake + ${PROJECT_BINARY_DIR}/ActsDD4hepGeometryModuleHelpers.cmake + COPYONLY +) # install cmake package configs install( @@ -85,6 +95,13 @@ install( DESTINATION ${install_package_config_dir}/Modules ) +install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/ActsGeometryModuleHelpers.cmake + ${CMAKE_CURRENT_LIST_DIR}/ActsDD4hepGeometryModuleHelpers.cmake + DESTINATION ${install_package_config_dir} +) + # install target configs for all available components foreach(_component ${_components}) install( diff --git a/cmake/ActsDD4hepGeometryModuleHelpers.cmake b/cmake/ActsDD4hepGeometryModuleHelpers.cmake new file mode 100644 index 00000000000..b2478dd290a --- /dev/null +++ b/cmake/ActsDD4hepGeometryModuleHelpers.cmake @@ -0,0 +1,37 @@ +function(acts_add_dd4hep_geometry_module target) + if(NOT UNIX) + message( + FATAL_ERROR + "Runtime geometry modules are only supported on Unix-like systems (Linux, macOS). " + "Cannot configure DD4hep geometry module target '${target}' on this platform." + ) + endif() + if( + NOT DEFINED ACTS_GEOMETRY_MODULE_ABI_TAG + OR ACTS_GEOMETRY_MODULE_ABI_TAG STREQUAL "" + ) + message( + FATAL_ERROR + "ACTS_GEOMETRY_MODULE_ABI_TAG is not set; cannot configure DD4hep geometry module target '${target}'." + ) + endif() + if(NOT DEFINED DD4hep_VERSION OR DD4hep_VERSION STREQUAL "") + message( + FATAL_ERROR + "DD4hep_VERSION is not set; cannot configure DD4hep geometry module target '${target}'." + ) + endif() + + set(_acts_dd4hep_geometry_module_abi_tag + "${ACTS_GEOMETRY_MODULE_ABI_TAG}|dd4hep-${DD4hep_VERSION}" + ) + + add_library(${target} SHARED ${ARGN}) + + target_link_libraries(${target} PRIVATE Acts::PluginDD4hep) + target_compile_definitions( + ${target} + PRIVATE + ACTS_GEOMETRY_MODULE_ABI_TAG="${_acts_dd4hep_geometry_module_abi_tag}" + ) +endfunction() diff --git a/cmake/ActsGeometryModuleHelpers.cmake b/cmake/ActsGeometryModuleHelpers.cmake new file mode 100644 index 00000000000..576cf97f7ca --- /dev/null +++ b/cmake/ActsGeometryModuleHelpers.cmake @@ -0,0 +1,26 @@ +function(acts_add_geometry_module target) + if(NOT UNIX) + message( + FATAL_ERROR + "Runtime geometry modules are only supported on Unix-like systems (Linux, macOS). " + "Cannot configure geometry module target '${target}' on this platform." + ) + endif() + if( + NOT DEFINED ACTS_GEOMETRY_MODULE_ABI_TAG + OR ACTS_GEOMETRY_MODULE_ABI_TAG STREQUAL "" + ) + message( + FATAL_ERROR + "ACTS_GEOMETRY_MODULE_ABI_TAG is not set; cannot configure geometry module target '${target}'." + ) + endif() + + add_library(${target} SHARED ${ARGN}) + + target_link_libraries(${target} PRIVATE Acts::Core) + target_compile_definitions( + ${target} + PRIVATE ACTS_GEOMETRY_MODULE_ABI_TAG="${ACTS_GEOMETRY_MODULE_ABI_TAG}" + ) +endfunction() diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 90b3ff98069..80235f809c4 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -32,6 +32,37 @@ add_library(docs-examples SHARED) set_target_properties(docs-examples PROPERTIES EXCLUDE_FROM_ALL TRUE) target_sources( docs-examples - PRIVATE examples/logging.cpp examples/units.cpp examples/errors.cpp + PRIVATE + examples/logging.cpp + examples/units.cpp + examples/errors.cpp + examples/geometry_module.cpp ) target_link_libraries(docs-examples PRIVATE Acts::Core) + +if(ACTS_BUILD_PLUGIN_DD4HEP) + target_link_libraries(docs-examples PRIVATE Acts::PluginDD4hep) + target_compile_definitions(docs-examples PRIVATE ACTS_BUILD_PLUGIN_DD4HEP) +endif() + +# Compile the module writing templates as real geometry modules so the +# snippet examples are verified at build time. +#! [Plain Module CMake] +include(ActsGeometryModuleHelpers) +acts_add_geometry_module( + docs-geometry-module-template + examples/geometry_module_template.cpp +) +#! [Plain Module CMake] +add_dependencies(docs-examples docs-geometry-module-template) + +if(ACTS_BUILD_PLUGIN_DD4HEP) + #! [DD4hep Module CMake] + include(ActsDD4hepGeometryModuleHelpers) + acts_add_dd4hep_geometry_module( + docs-geometry-module-template-dd4hep + examples/geometry_module_template_dd4hep.cpp + ) + #! [DD4hep Module CMake] + add_dependencies(docs-examples docs-geometry-module-template-dd4hep) +endif() diff --git a/docs/examples/geometry_module.cpp b/docs/examples/geometry_module.cpp new file mode 100644 index 00000000000..683cb5fa4e8 --- /dev/null +++ b/docs/examples/geometry_module.cpp @@ -0,0 +1,41 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "Acts/Geometry/GeometryModuleLoader.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include + +#ifdef ACTS_BUILD_PLUGIN_DD4HEP +#include "ActsPlugins/DD4hep/GeometryModuleLoader.hpp" + +#include +#endif + +void exampleLoadPlainModule(const Acts::Logger& logger, + const std::filesystem::path& modulePath) { + //! [Load Plain Module] + auto geometry = Acts::loadGeometryModule(modulePath, logger); + // 'geometry' is a std::shared_ptr. + // The shared library stays loaded until 'geometry' is destroyed. + //! [Load Plain Module] + (void)geometry; +} + +#ifdef ACTS_BUILD_PLUGIN_DD4HEP +void exampleLoadDD4hepModule(const dd4hep::Detector& detector, + const Acts::Logger& logger, + const std::filesystem::path& modulePath) { + //! [Load DD4hep Module] + auto geometry = Acts::loadDD4hepGeometryModule(modulePath, detector, logger); + // 'geometry' is a std::shared_ptr. + // The shared library stays loaded until 'geometry' is destroyed. + //! [Load DD4hep Module] + (void)geometry; +} +#endif diff --git a/docs/examples/geometry_module_template.cpp b/docs/examples/geometry_module_template.cpp new file mode 100644 index 00000000000..9f2c97f72bc --- /dev/null +++ b/docs/examples/geometry_module_template.cpp @@ -0,0 +1,25 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! [Write Plain Module] +#include "Acts/Geometry/GeometryModuleHelper.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" + +#include + +namespace { +std::unique_ptr buildMyGeometry( + const Acts::Logger& logger) { + ACTS_INFO("Building my geometry"); + // ... construct and return a TrackingGeometry ... + return nullptr; +} +} // namespace + +ACTS_DEFINE_GEOMETRY_MODULE(buildMyGeometry) +//! [Write Plain Module] diff --git a/docs/examples/geometry_module_template_dd4hep.cpp b/docs/examples/geometry_module_template_dd4hep.cpp new file mode 100644 index 00000000000..869594212c3 --- /dev/null +++ b/docs/examples/geometry_module_template_dd4hep.cpp @@ -0,0 +1,28 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! [Write DD4hep Module] +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "ActsPlugins/DD4hep/GeometryModuleHelper.hpp" + +#include + +#include + +namespace { +std::unique_ptr buildMyGeometry( + const dd4hep::Detector& detector, const Acts::Logger& logger) { + (void)detector; + ACTS_INFO("Building DD4hep geometry"); + // ... use detector to build and return a TrackingGeometry ... + return nullptr; +} +} // namespace + +ACTS_DEFINE_DD4HEP_GEOMETRY_MODULE(buildMyGeometry) +//! [Write DD4hep Module] diff --git a/docs/groups/detector_descr/runtime_geometry_modules.md b/docs/groups/detector_descr/runtime_geometry_modules.md new file mode 100644 index 00000000000..fd6815f6b80 --- /dev/null +++ b/docs/groups/detector_descr/runtime_geometry_modules.md @@ -0,0 +1,167 @@ +@defgroup geometry_module_loading Runtime geometry module loading +@ingroup geometry +@brief Loading tracking geometries from runtime shared libraries. + +This topic documents the runtime geometry module loading entry points: + +- @ref Acts::loadGeometryModule for modules that do not require user data. +- @ref Acts::loadDD4hepGeometryModule for DD4hep-based modules. + +Both loaders validate module ABI compatibility before constructing the +@ref Acts::TrackingGeometry, and keep the shared library loaded for the +lifetime of the returned geometry object. + +## Overview + +Runtime geometry modules allow tracking geometries to be compiled as +independent shared libraries (`.so`/`.dylib`) and loaded at runtime without +recompiling the host application. This is useful when: + +- The geometry changes frequently and recompiling the full framework is costly. +- Different geometry variants need to be swapped at runtime. +- The geometry is developed and distributed independently from the experiment + framework. + +@note Runtime geometry modules rely on `dlopen`/`dlsym` and are + only supported on Unix-like systems (Linux, macOS). + +## The module ABI + +Every geometry module must export a single C-linkage entry point: + +```c +extern "C" const ActsGeometryModuleV1* acts_geometry_module_v1(void); +``` + +The returned `ActsGeometryModuleV1` struct (defined in +`Acts/Geometry/GeometryModule.h`) carries four fields: + +| Field | Type | Purpose | +|--------------------|-------------------------------------------|----------------------------------------------------------| +| `module_abi_tag` | `const char*` | Build-time ABI tag matched against the host library | +| `user_data_type` | `const char*` | Type name of the extra context the module needs, or null | +| `build` | `void* (*)(const void* user_data, const void* logger)` | Constructs the geometry; returns a heap-allocated `TrackingGeometry*` or null on failure | +| `destroy` | `void (*)(void* handle)` | Deletes the geometry object returned by `build` | + +You never fill this struct manually. Use the provided helper macros instead +(see @ref geometry_module_writing "Writing a geometry module" below). + +## ABI compatibility + +Each ACTS build is tagged with an opaque string (`ACTS_GEOMETRY_MODULE_ABI_TAG`) +that encodes the host library ABI. Plain geometry modules use that tag as-is +and are matched against the tag compiled into `ActsCore`. + +DD4hep geometry modules use a DD4hep-specific extension of that tag: + +```text +${ACTS_GEOMETRY_MODULE_ABI_TAG}|dd4hep-${DD4hep_VERSION} +``` + +`loadDD4hepGeometryModule` matches `module_abi_tag` against the corresponding +tag compiled into `Acts::PluginDD4hep`, so a DD4hep module must be built +against both the same ACTS build and the same DD4hep version as the host +plugin. Any mismatch causes an immediate `std::runtime_error`. + +## Loading a module + +### Plain module (no extra context) + +Include `Acts/Geometry/GeometryModuleLoader.hpp`, then: + +@snippet{trimleft} examples/geometry_module.cpp Load Plain Module + +### DD4hep module + +Include `ActsPlugins/DD4hep/GeometryModuleLoader.hpp`, then: + +@snippet{trimleft} examples/geometry_module.cpp Load DD4hep Module + +The loader passes a pointer to `detector` through the opaque `void* user_data` +argument. It validates that the module declares `user_data_type == "dd4hep::Detector"`; +trying to load a plain module with `loadDD4hepGeometryModule` (or vice-versa) +throws a descriptive `std::runtime_error`. + +@anchor geometry_module_writing +## Writing a geometry module + +### Plain module + +1. Create a source file with a build function: + +@snippet{trimleft} examples/geometry_module_template.cpp Write Plain Module + +2. Register it in CMake using `acts_add_geometry_module`: + +@snippet{trimleft} CMakeLists.txt Plain Module CMake + +The CMake helper creates a `SHARED` library target, links it against +`Acts::Core`, and injects the `ACTS_GEOMETRY_MODULE_ABI_TAG` compile definition +required by `ACTS_DEFINE_GEOMETRY_MODULE`. + +### DD4hep module + +1. Create a source file with a build function that takes a `dd4hep::Detector`: + +@snippet{trimleft} examples/geometry_module_template_dd4hep.cpp Write DD4hep Module + +2. Register it in CMake using `acts_add_dd4hep_geometry_module`: + +@snippet{trimleft} CMakeLists.txt DD4hep Module CMake + +This links against `Acts::PluginDD4hep` instead of `Acts::Core` and sets +`ACTS_GEOMETRY_MODULE_ABI_TAG` to the DD4hep-specific tag derived from the +installed ACTS tag plus `DD4hep_VERSION`. + +## Lifetime management + +The `std::shared_ptr` returned by both loaders uses a custom +deleter that: + +1. Calls `descriptor->destroy()` to delete the `TrackingGeometry` object via + the module's own destructor (important for correct cross-boundary `delete`). +2. Keeps a `shared_ptr` to the `dlopen` handle alive, so the shared + library is only unloaded **after** the geometry is destroyed — preventing + use-after-unload of virtual dispatch tables or static data. + +## Error handling + +All errors are reported as `std::runtime_error`. The loader performs several +checks in order before invoking the build function: + +- **File not found.** The path is checked with `std::filesystem::exists` before + calling `dlopen`. This gives a clearer error than the linker error that + `dlopen` would produce for a missing file. + +- **`dlopen` failure.** If the operating system cannot load the shared library + (e.g. missing transitive dependencies, wrong architecture), the error string + from `dlerror` is included in the exception message. + +- **Missing entry point.** The loader looks up the symbol + `acts_geometry_module_v1` via `dlsym`. If it is absent the module was either + not built with `ACTS_DEFINE_GEOMETRY_MODULE` / `ACTS_DEFINE_DD4HEP_GEOMETRY_MODULE`, + or the symbol was stripped or hidden by the linker. + +- **Null or incomplete descriptor.** The struct returned by the entry point must + be non-null and have `module_abi_tag` set and both function-pointer fields (`build`, `destroy`) + non-null. A null or partially initialized descriptor indicates a + programming error in the module. + +- **ABI tag mismatch.** `module_abi_tag` is compared against the tag baked into + the host loader library at its own build time (`ActsCore` for plain modules, + `Acts::PluginDD4hep` for DD4hep modules). A mismatch means the module and the + host were built from different ACTS or DD4hep versions and cannot safely + interoperate. Rebuild the module against the same ACTS installation and, for + DD4hep modules, the same DD4hep version. + +- **User-data type mismatch.** The `user_data_type` field in the descriptor is + compared against the type the loader expects. If you call `loadGeometryModule` + on a module that declares a `user_data_type` (i.e. it needs extra context such + as a `dd4hep::Detector`), or call a typed loader (e.g. `loadDD4hepGeometryModule`) + on a plain module, a `std::runtime_error` is thrown with a hint pointing to + the correct loader to use. + +- **`build` returns null.** The module's build function returned a null pointer, + which means it failed to construct the geometry. Exceptions thrown inside + `build` are caught by the helper and logged via `ACTS_ERROR` before the null + is returned, so check the log for the underlying cause.