Repository: celeritas-project/celeritas
Pull request #1673: Define dependency helper targets and reorganize CMakeLists
Author: login: sethrj
Labels: name: core
description: Software engineering infrastructure (corecel)
name: minor
description: Refactoring or minor internal changes/fixes
Created: 2025-03-15T23:20:11Z | Merged: 2025-03-18T18:01:53Z

This adds libraries `Celeritas::ExtMPI`, `Celeritas::ExtOpenMP`, etc. that are always defined but transitively link to dependencies such as MPI and OpenMP when the package is enabled. It allows easier use of optional code and lets us define object libraries to further restrict the propagation of flags and includes from upstream packages for faster builds and better ccache use. 

- sincospi flag setup moved to where we determine system dependencies
- flag setup moved to a new interface library `BuildFlags` that lives in `src/`
- new external interface libraries in `src/`
- DeviceToolkit definition moved from the main cmakelists to `src/`, and `CMAKE_DL_LIBS` link moved there as well
- Perfetto alias is now `Celeritas::ExtPerfetto`


Diff:
diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index 772b4cac21..34e7238ac7 100644
--- a/.github/workflows/build-docker.yml
+++ b/.github/workflows/build-docker.yml
@@ -87,7 +87,7 @@ jobs:
             celer-sim celer-g4; do
             test -x "bin/${exe}"
           done
-          ./bin/celer-sim --version
+          ./bin/celer-sim --config
       - name: Build examples
         # TODO: rocm+ndebug fails to propagate HIP library link
         if: >-
diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml
index 5f9b53d2ca..961b11d68b 100644
--- a/.github/workflows/build-fast.yml
+++ b/.github/workflows/build-fast.yml
@@ -104,7 +104,7 @@ jobs:
       - name: Check installation
         working-directory: install
         run: |
-          ./bin/celer-sim --version
+          ./bin/celer-sim --config
       - name: Show ccache stats
         if: ${{!cancelled()}}
         run: |
@@ -177,7 +177,7 @@ jobs:
       - name: Check installation
         working-directory: install
         run: |
-          ./bin/celer-sim --version
+          ./bin/celer-sim --config
       - name: Show ccache stats
         if: ${{!cancelled()}}
         run: |
diff --git a/.github/workflows/build-ultralite.yml b/.github/workflows/build-ultralite.yml
index 3692e30cd8..0b3e5f5ebc 100644
--- a/.github/workflows/build-ultralite.yml
+++ b/.github/workflows/build-ultralite.yml
@@ -58,6 +58,7 @@ jobs:
         working-directory: install
         run: |
           ./bin/celer-sim --version
+          ./bin/celer-sim --config
       - name: Show ccache stats
         if: ${{!cancelled()}}
         run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9223865ba..b9012e60e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -499,6 +499,26 @@ if(CELERITAS_BUILD_TESTS)
   celeritas_find_or_builtin_package(GTest 1.10)
 endif()
 
+#----------------------------------------------------------------------------#
+# System property: availability of sincospi and related functions:
+# All of: `^${CELERITAS_SINCOSPI_PREFIX}(sincos(pi)?|(sin|cos)pi)f?$`
+if(NOT DEFINED CELERITAS_SINCOSPI_PREFIX)
+  foreach(_prefix "__" "_" "")
+    try_compile(CELERITAS_TRY_${_prefix}SINCOSPI
+      # NOTE: the line below is required for CMake < 3.25
+      "${CMAKE_BINARY_DIR}/CMakeFiles/CMakeScratch/sincospi${_prefix}"
+      SOURCES "${PROJECT_SOURCE_DIR}/cmake/try-sincospi.cc"
+      COMPILE_DEFINITIONS "-DCELERITAS_SINCOSPI_PREFIX=${_prefix}"
+    )
+    if(CELERITAS_TRY_${_prefix}SINCOSPI)
+      set(CELERITAS_SINCOSPI_PREFIX "${_prefix}" CACHE INTERNAL
+        "Prefix for compiler-provided sincospi and related functions"
+      )
+      break()
+    endif()
+  endforeach()
+endif()
+
 #----------------------------------------------------------------------------#
 # BUILTINS
 #----------------------------------------------------------------------------#
@@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
+# Load our local copy in case a dependency hasn't loaded a newer one
+# and install it alongside CeleritasConfig (see further below)
 set(_LOCAL_RDCUTILS_FILENAME "${PROJECT_SOURCE_DIR}/cmake/external/CudaRdcUtils.cmake")
 include("${_LOCAL_RDCUTILS_FILENAME}")
 
-#----------------------------------------------------------------------------#
-# LIBRARY
-#----------------------------------------------------------------------------#
-
+# NOTE: include library utils after RDC
 include(CeleritasLibraryUtils)
 
-# Define an interface library for propagating include paths for GPU compilers.
-# This allow API calls, so long as the compiler or linker implicitly bring in
-# the correct CUDA/ROCM libraries. Those libraries are *not* explicitly linked
-# because mistakenly linking against both cudart and cudart_static (one of which
-# might come from upstream libraries such as vecgeom) can cause nasty link- and
-# run-time errors.
-add_library(celeritas_device_toolkit INTERFACE)
-add_library(Celeritas::DeviceToolkit ALIAS celeritas_device_toolkit)
-
-if(CELERITAS_USE_CUDA)
-  target_link_libraries(celeritas_device_toolkit INTERFACE CUDA::toolkit)
-elseif(CELERITAS_USE_HIP)
-  if(CMAKE_HIP_COMPILER_ROCM_ROOT)
-    # Undocumented CMake variable
-    set(ROCM_PATH "${CMAKE_HIP_COMPILER_ROCM_ROOT}")
-  else()
-    # This hack works on Crusher as of ROCm 5.1.0
-    set(ROCM_PATH "$ENV{ROCM_PATH}" CACHE PATH "Path to ROCm headers")
-  endif()
-  target_include_directories(celeritas_device_toolkit
-    SYSTEM INTERFACE "${ROCM_PATH}/include"
-  )
-  find_library(ROCTX_LIBRARY roctx64)
-  if(ROCTX_LIBRARY)
-    set(CELERITAS_HAVE_ROCTX ON)
-    target_link_libraries(celeritas_device_toolkit INTERFACE "${ROCTX_LIBRARY}")
-  endif()
-  if(NOT BUILD_SHARED_LIBS)
-    # Downstream libs don't link against correct HIP dependencies when using
-    # static libraries, and even though the code below propagates the library
-    # names (-lamdhip64) CMake fails to include the link directories
-    # (/opt/rocm/lib)
-    target_link_libraries(celeritas_device_toolkit
-      INTERFACE "$<INSTALL_INTERFACE:${CMAKE_HIP_IMPLICIT_LINK_LIBRARIES}>"
-    )
-    target_link_directories(celeritas_device_toolkit
-      INTERFACE "$<INSTALL_INTERFACE:${CMAKE_HIP_IMPLICIT_LINK_DIRECTORIES}>"
-    )
-  endif()
-endif()
-
-install(TARGETS celeritas_device_toolkit
-  EXPORT celeritas-targets
-)
-
 #----------------------------------------------------------------------------#
-if(NOT DEFINED CELERITAS_SINCOSPI_PREFIX)
-  foreach(_prefix "__" "_" "")
-    try_compile(CELERITAS_TRY_${_prefix}SINCOSPI
-      # NOTE: the line below is required for CMake < 3.25
-      "${CMAKE_BINARY_DIR}/CMakeFiles/CMakeScratch/sincospi${_prefix}"
-      SOURCES "${PROJECT_SOURCE_DIR}/cmake/try-sincospi.cc"
-      COMPILE_DEFINITIONS "-DCELERITAS_SINCOSPI_PREFIX=${_prefix}"
-    )
-    if(CELERITAS_TRY_${_prefix}SINCOSPI)
-      set(CELERITAS_SINCOSPI_PREFIX "${_prefix}" CACHE INTERNAL
-        "Prefix for compiler-provided sincospi and related functions"
-      )
-      break()
-    endif()
-  endforeach()
-endif()
-
+# LIBRARY
 #----------------------------------------------------------------------------#
+
 # Add the main libraries
 add_subdirectory(src)
 
@@ -629,9 +579,9 @@ set(CELERITAS_INSTALL_CMAKECONFIGDIR
 
 # Build list of CMake files to install
 set(_cmake_files
-  # DEPRECATED: remove in v1.0
   "${PROJECT_SOURCE_DIR}/cmake/CeleritasLibrary.cmake"
-  # TODO: this might not need installing if VecGeom provides a new version
+  # TODO: in the future, don't include or distribute RdcUtils.
+  # For now, include in case downstream projects do `include(CudaRdcUtils)`.
   "${_LOCAL_RDCUTILS_FILENAME}"
 )
 foreach(_dep Geant4 HepMC3 ROOT Thrust VecGeom)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index a8d7f9eee7..e15fe72a53 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -47,7 +47,7 @@ celeritas_add_library(celer_app_utils STATIC
 
 celeritas_target_link_libraries(celer_app_utils
   PUBLIC CLI11::CLI11
-  PRIVATE Celeritas::corecel Celeritas::corecel_mpi
+  PRIVATE Celeritas::corecel Celeritas::ExtMPI
 )
 
 celeritas_target_include_directories(celer_app_utils
diff --git a/app/celer-geo/CMakeLists.txt b/app/celer-geo/CMakeLists.txt
index eab1e4009e..b3eb188c21 100644
--- a/app/celer-geo/CMakeLists.txt
+++ b/app/celer-geo/CMakeLists.txt
@@ -16,8 +16,11 @@ set(LIBRARIES
   nlohmann_json::nlohmann_json
   CLI11::CLI11
   celer_app_utils
+  Celeritas::ExtGeant4Geo
+  Celeritas::ExtVecGeom
 )
 
+# Add the executable
 celeritas_add_executable(celer-geo ${SOURCES})
 celeritas_target_link_libraries(celer-geo ${LIBRARIES})
 
diff --git a/app/celer-sim/CMakeLists.txt b/app/celer-sim/CMakeLists.txt
index 2172738c06..ea3b652213 100644
--- a/app/celer-sim/CMakeLists.txt
+++ b/app/celer-sim/CMakeLists.txt
@@ -13,19 +13,13 @@ set(SOURCES
 )
 set(LIBRARIES
   Celeritas::celeritas
-  Celeritas::DeviceToolkit
+  Celeritas::ExtDeviceApi
+  Celeritas::ExtOpenMP
   nlohmann_json::nlohmann_json
   CLI11::CLI11
   celer_app_utils
 )
 
-if(CELERITAS_USE_CUDA AND CELERITAS_CORE_GEO STREQUAL "VecGeom")
-  list(APPEND LIBRARIES VecGeom::vecgeom)
-endif()
-if(CELERITAS_USE_OpenMP)
-  list(APPEND LIBRARIES OpenMP::OpenMP_CXX)
-endif()
-
 # Add the executable
 celeritas_add_executable(celer-sim ${SOURCES})
 celeritas_target_link_libraries(celer-sim PRIVATE ${LIBRARIES})
diff --git a/cmake/CeleritasConfig.cmake.in b/cmake/CeleritasConfig.cmake.in
index aa03f2b9fa..096791282f 100644
--- a/cmake/CeleritasConfig.cmake.in
+++ b/cmake/CeleritasConfig.cmake.in
@@ -177,6 +177,8 @@ cmake_policy(POP)
 if(NOT TARGET celeritas)
   # Load Celeritas-distributed RDC utils after potential older version
   # in dependencies
+  # TODO: in the future, rely on its inclusion via upstream VecGeom, and only
+  # include here if VecGeom+CUDA are enabled.
   include("${CELERITAS_CMAKE_DIR}/CudaRdcUtils.cmake" OPTIONAL)
 
   # Load targets created by `install(EXPORT celeritas-targets`
diff --git a/cmake/CeleritasLibrary.cmake b/cmake/CeleritasLibrary.cmake
index a5077b565e..e883552c86 100644
--- a/cmake/CeleritasLibrary.cmake
+++ b/cmake/CeleritasLibrary.cmake
@@ -6,7 +6,8 @@
 CeleritasLibrary
 ----------------
 
-Deprecated file for use by downstream code. TODO: remove in v1.0.
+This file is to be replaced with the wrappers from CeleritasLibraryUtils and
+included automatically in Celeritas and downstream projects.
 
 #]=======================================================================]
 
diff --git a/cmake/CeleritasLibraryUtils.cmake b/cmake/CeleritasLibraryUtils.cmake
index 78c18bac95..005696ee57 100644
--- a/cmake/CeleritasLibraryUtils.cmake
+++ b/cmake/CeleritasLibraryUtils.cmake
@@ -14,34 +14,23 @@ forward directly to CMake or alternatively to CudaRdc.
 
   Add a library that correctly links against CUDA relocatable device code, has
   the ``Celeritas::`` aliases, is generated into the ``lib/`` build
-  directory, and is installed with the project.
+  directory, automatically uses HIP for ``.cu`` files, and is installed with the
+  project.
 
 .. command:: celeritas_add_test_library
 
   Add a test-only library that uses RDC but does not get installed.
 
-.. command:: celeritas_get_cuda_source_args
-
-  Get a list of all source files in the argument that are CUDA language.
-
-    celeritas_get_cuda_source_args(<var> [<source> ...])
-
-.. command:: celeritas_add_library
-
-  Add a library that correctly links against CUDA relocatable device code.
+.. command:: celeritas_add_interface_library
 
-.. command:: celeritas_install
-
-  Install library that correctly deal with CUDA relocatable device code.
-
-.. command:: celeritas_set_target_properties
-
-  Install library that correctly deal with CUDA relocatable device code extra
-  libraries.
+  Add an interface library that correctly links against CUDA relocatable device
+  code, has the ``Celeritas::`` aliases, and is installed with the project.
+  Interface libraries should have capitalized names to distinguish them.
 
 .. command:: celeritas_add_object_library
 
-  Add an OBJECT library to reduce dependencies (e.g. includes) from other libraries.
+  Add an OBJECT library to reduce dependencies (e.g. includes) from other
+  libraries. The source files MUST NOT be CUDA/HIP.
 
 .. command:: celeritas_add_executable
 
@@ -57,6 +46,27 @@ forward directly to CMake or alternatively to CudaRdc.
   The ``<source>`` arguments are passed to CMake's builtin ``add_executable``
   command.
 
+.. command:: celeritas_configure_file
+
+  Configure to the build "include" directory for later installation::
+
+    celeritas_configure_file(<input> <output> [ARGS...])
+
+  The ``<input>`` must be a relative path to the current source directory, and
+  the ``<output>` path is configured to the project build "include" directory.
+
+.. command:: celeritas_polysource_append
+
+  Add C++ and CUDA/HIP source files based on the enabled options.
+
+    celeritas_polysource_append(SOURCES my/Class)
+
+Wrapper functions
+^^^^^^^^^^^^^^^^^
+
+These functions dispatch to the CudaRdcUtils if necessary (i.e., if CUDA and
+VecGeom are enabled).
+
 .. command:: celeritas_target_link_libraries
 
   Specify libraries or flags to use when linking a given target and/or its
@@ -112,20 +122,33 @@ forward directly to CMake or alternatively to CudaRdc.
 
   See ``target_compile_options`` for additional detail.
 
-.. command:: celeritas_configure_file
 
-  Configure to the build "include" directory for later installation::
+Helper functions
+^^^^^^^^^^^^^^^^
 
-    celeritas_configure_file(<input> <output> [ARGS...])
+.. command:: celeritas_get_cuda_source_args
 
-  The ``<input>`` must be a relative path to the current source directory, and
-  the ``<output>` path is configured to the project build "include" directory.
+  Get a list of all source files in the argument that are CUDA language.
 
-.. command:: celeritas_polysource_append
+    celeritas_get_cuda_source_args(<var> [<source> ...])
 
-  Add C++ and CUDA/HIP source files based on the enabled options.
+Wrapper functions
+^^^^^^^^^^^^^^^^^
 
-    celeritas_polysource_append(SOURCES my/Class)
+These are to be moved to CeleritasLibrary.
+
+.. command:: celeritas_add_library
+
+  Add a library that correctly links against CUDA relocatable device code.
+
+.. command:: celeritas_install
+
+  Install library that correctly deal with CUDA relocatable device code.
+
+.. command:: celeritas_set_target_properties
+
+  Install library that correctly deal with CUDA relocatable device code extra
+  libraries.
 
 #]=======================================================================]
 include_guard(GLOBAL)
@@ -256,7 +279,7 @@ function(celeritas_add_test_library target)
     endif()
   endif()
 
-  celeritas_add_library(${target} ${ARGN})
+  celeritas_add_library(${ARGV})
 
   if(CELERITAS_USE_ROOT)
     # Require PIC for static libraries, including "object" RDC lib
@@ -267,8 +290,19 @@ function(celeritas_add_test_library target)
 endfunction()
 
 #-----------------------------------------------------------------------------#
-# Add an object library to limit the propagation of includes to the rest of the
-# library.
+
+function(celeritas_add_interface_library target)
+  # Interface libraries don't need RDC wrappers
+  add_library(${target} INTERFACE ${ARGN})
+  add_library(Celeritas::${target} ALIAS ${target})
+  install(TARGETS ${target}
+    EXPORT celeritas-targets
+    COMPONENT runtime
+  )
+endfunction()
+
+#-----------------------------------------------------------------------------#
+
 function(celeritas_add_object_library target)
   add_library(${target} OBJECT ${ARGN})
   install(TARGETS ${target}
@@ -282,7 +316,7 @@ endfunction()
 #-----------------------------------------------------------------------------#
 
 function(celeritas_add_executable target)
-  add_executable("${target}" ${ARGN})
+  add_executable(${ARGV})
   install(TARGETS "${target}"
     EXPORT celeritas-targets
     RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
diff --git a/cmake/external/CudaRdcUtils.cmake b/cmake/external/CudaRdcUtils.cmake
index ee61ce6dee..b100c2cf5f 100644
--- a/cmake/external/CudaRdcUtils.cmake
+++ b/cmake/external/CudaRdcUtils.cmake
@@ -125,6 +125,8 @@ relocatable device code and most importantly linking against those libraries.
 set(_CUDA_RDC_VERSION 1)
 if(CUDA_RDC_VERSION GREATER _CUDA_RDC_VERSION)
   # A newer version has already been loaded
+  message(VERBOSE "Ignoring CUDA_RDC_VERSION ${_CUDA_RDC_VERSION}: "
+    "already loaded ${CUDA_RDC_VERSION}")
   return()
 endif()
 set(CUDA_RDC_VERSION ${_CUDA_RDC_VERSION})
diff --git a/doc/usage/integration.rst b/doc/usage/integration.rst
index f46f841a52..9d831badc8 100644
--- a/doc/usage/integration.rst
+++ b/doc/usage/integration.rst
@@ -6,25 +6,29 @@
 .. _library:
 
 Software library
-----------------
+================
 
 The most stable part of Celeritas is, at the present time, the high-level
 :ref:`api_g4_interface`. However, many other
 components of the API are stable and documented in the :code:`api` section.
 
 CMake integration
-^^^^^^^^^^^^^^^^^
+-----------------
 
 The Celeritas library is most easily used when your downstream app is built with
-CMake. It should require a single line to initialize::
+CMake, demonstrated by the :ref:`example_minimal` and :ref:`example_cmake` examples. It should require a single line to initialize::
 
-   find_package(Celeritas REQUIRED CONFIG)
+   find_package(Celeritas REQUIRED)
 
 and a single line to link::
 
    cuda_rdc_target_link_libraries(mycode PUBLIC Celeritas::celeritas)
 
-This special ``cuda_rdc_`` prefix, rather than the simpler native CMake command
+CudaRdcUtils
+^^^^^^^^^^^^
+
+The special ``cuda_rdc_`` command in the previous statement,
+rather than the simpler native CMake command
 ``target_link_libraries``, is needed in case Celeritas is built with both
 CUDA and the current version of VecGeom, which uses a special but messy feature
 called CUDA Relocatable Device Code (RDC).
@@ -40,6 +44,10 @@ and whether Celeritas is CPU-only or CUDA enabled::
   cuda_rdc_add_executable(myapplication ...)
   cuda_rdc_target_link_libraries(myapplication PRIVATE myconsumer)
 
+.. note:: Prior to Celeritas v0.6, obtaining the above commands required the
+   user to include :file:`CudaRdcUtils.cmake` manually. Now, it is included
+   automatically.
+
 If your project builds shared libraries that are intended to be loaded at
 application runtime (e.g. via ``dlopen``), you should prefer use the CMake
 ``MODULE`` target type::
@@ -56,7 +64,9 @@ and its device code counterpart::
 
   add_library(mybadplugin SHARED ...)
   # ... or myproject_add_library(mybadplugin ...)
-  target_link_libraries(mybadplugin PRIVATE Celeritas::celeritas $<TARGET_NAME_IF_EXISTS:Celeritas::celeritas_final>)
+  target_link_libraries(mybadplugin
+    PRIVATE Celeritas::celeritas $<TARGET_NAME_IF_EXISTS:Celeritas::celeritas_final>
+  )
   # ... or otherwise declare the plugin as requiring linking to the two targets
 
 Celeritas device code counterpart target names are always the name of the
@@ -64,12 +74,25 @@ primary target appended with ``_final``. They are only present if Celeritas was
 built with CUDA support so it is recommended to use the CMake generator
 expression above to support CUDA or CPU-only builds transparently.
 
-The :ref:`example_minimal` and :ref:`example_cmake` examples demonstrate how to
-use Celeritas as a library with a short standalone CMake project and with
-Geant4.
+Targets
+^^^^^^^
+
+CMake targets exported by Celeritas live in the ``Celeritas::`` namespace.
+These targets are:
+
+- An interface lib ``BuildFlags`` that provides the include path to Celeritas,
+  language requirements, and warning overrides;
+- Wrapper interface libraries that provide links to optional dependencies if
+  enabled: ``ExtMPI``, ``ExtOpenMP``, ``ExtPerfetto``, ``ExtGeant4Geo``
+  (G4Geometry if available), and ``ExtDeviceApi`` (CUDA::toolkit if available,
+  ROCM/HIP libraries and includes, and roctx64 if available);
+- Code libraries described in :ref:`api`: corecel, geocel, orange,
+  celeritas, and accel;
+- Executables such as ``celer-sim`` and ``celer-geo``.
+
 
 App integration
-^^^^^^^^^^^^^^^
+---------------
 
 Integrating with Geant4 user applications and experiment frameworks requires
 setting up:
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index aad7f3f653..26b2a032ec 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -135,7 +135,6 @@ endforeach()
 if(CELERITAS_BUILTIN_Perfetto AND perfetto_POPULATED)
   set(Perfetto_INCLUDE_DIR ${perfetto_SOURCE_DIR}/sdk)
   add_library(celeritas_perfetto STATIC ${Perfetto_INCLUDE_DIR}/perfetto.cc)
-  add_library(Celeritas::Perfetto ALIAS celeritas_perfetto)
   target_include_directories(celeritas_perfetto INTERFACE ${Perfetto_INCLUDE_DIR})
 endif()
 
diff --git a/scripts/ci/test-examples.sh b/scripts/ci/test-examples.sh
index f67395b78a..c102335543 100755
--- a/scripts/ci/test-examples.sh
+++ b/scripts/ci/test-examples.sh
@@ -26,7 +26,7 @@ build_local() {
   printf "\e[32mTesting in ${PWD}\e[m\n"
   mkdir build
   cd build
-  cmake -G Ninja \
+  cmake -G Ninja --log-level=verbose \
     -D CMAKE_INSTALL_PREFIX=${EXAMPLE_INSTALL} \
     ..
   ninja
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 79619df7b7..43888cf4a7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,6 +3,137 @@
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 #----------------------------------------------------------------------------#
 
+#----------------------------------------------------------------------------#
+# INTERFACE LIBRARIES
+#----------------------------------------------------------------------------#
+
+# Add a library for propagating common build flags and Celeritas includes
+celeritas_add_src_library(BuildFlags INTERFACE)
+
+# Require at least C++17
+target_compile_features(BuildFlags INTERFACE cxx_std_17)
+if(CELERITAS_USE_CUDA)
+  target_compile_features(BuildFlags INTERFACE cuda_std_17)
+endif()
+if(WIN32)
+  target_compile_definitions(BuildFlags INTERFACE
+    $<$<COMPILE_LANGUAGE:CXX>:NOMINMAX NOGDI>
+  )
+
+  if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
+    # __cplusplus: use standard-compliant C++ version macro
+    # preprocessor: use standard-compliant preprocessor
+    # EHsc: enable standard C++ exception handling
+    target_compile_options(BuildFlags INTERFACE
+      $<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus /Zc:preprocessor /EHsc>
+    )
+  endif()
+
+  # Inheriting via dominance is correct behavior
+  target_compile_options(BuildFlags
+    INTERFACE "$<$<COMPILE_LANGUAGE:CXX>:/wd4267$<SEMICOLON>/wd4250>"
+  )
+endif()
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
+    AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13)
+  # GCC 13 and 14 have *lots* of false positives, and not just on our project
+  # see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107532
+  celeritas_target_compile_options(BuildFlags
+    INTERFACE "$<$<COMPILE_LANGUAGE:CXX>:-Wno-dangling-reference>"
+  )
+endif()
+
+celeritas_target_include_directories(BuildFlags
+  INTERFACE
+    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
+    "$<BUILD_INTERFACE:${CELERITAS_HEADER_CONFIG_DIRECTORY}>"
+    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+
+#----------------------------------------------------------------------------#
+
+# Define an interface library for propagating include paths for GPU compilers.
+# This allow API calls, so long as the compiler or linker implicitly bring in
+# the correct CUDA/ROCM libraries. Those libraries are *not* explicitly linked
+# because mistakenly linking against both cudart and cudart_static (one of which
+# might come from upstream libraries such as vecgeom) can cause nasty link- and
+# run-time errors.
+celeritas_add_interface_library(ExtDeviceApi)
+
+if(CELERITAS_USE_CUDA)
+  target_link_libraries(ExtDeviceApi INTERFACE CUDA::toolkit)
+
+  # NVTX requires `-ldl`
+  # This does not appears to be needed anymore with
+  # CMake 3.26.5 and CUDA sdk 12.6.
+  target_link_libraries(ExtDeviceApi INTERFACE ${CMAKE_DL_LIBS})
+elseif(CELERITAS_USE_HIP)
+  if(CMAKE_HIP_COMPILER_ROCM_ROOT)
+    # Undocumented CMake variable
+    set(ROCM_PATH "${CMAKE_HIP_COMPILER_ROCM_ROOT}")
+  else()
+    # This works on some older OLCF environment
+    set(ROCM_PATH "$ENV{ROCM_PATH}" CACHE PATH "Path to ROCm headers")
+  endif()
+  target_include_directories(ExtDeviceApi
+    SYSTEM INTERFACE "${ROCM_PATH}/include"
+  )
+  find_library(ROCTX_LIBRARY roctx64)
+  if(ROCTX_LIBRARY)
+    set(CELERITAS_HAVE_ROCTX ON)
+    target_link_libraries(ExtDeviceApi INTERFACE "${ROCTX_LIBRARY}")
+  endif()
+  if(NOT BUILD_SHARED_LIBS)
+    # Downstream libs don't link against correct HIP dependencies when using
+    # static libraries, and even though the code below propagates the library
+    # names (-lamdhip64) CMake fails to include the link directories
+    # (/opt/rocm/lib)
+    target_link_libraries(ExtDeviceApi
+      INTERFACE "$<INSTALL_INTERFACE:${CMAKE_HIP_IMPLICIT_LINK_LIBRARIES}>"
+    )
+    target_link_directories(ExtDeviceApi
+      INTERFACE "$<INSTALL_INTERFACE:${CMAKE_HIP_IMPLICIT_LINK_DIRECTORIES}>"
+    )
+  endif()
+endif()
+
+#----------------------------------------------------------------------------#
+
+# Add libraries for optional external dependencies
+macro(celer_add_ext_library pkg)
+  celeritas_add_interface_library(Ext${pkg})
+
+  if(CELERITAS_USE_${pkg})
+    target_link_libraries(Ext${pkg} INTERFACE ${ARGN})
+  endif()
+endmacro()
+
+celer_add_ext_library(MPI MPI::MPI_CXX)
+celer_add_ext_library(OpenMP OpenMP::OpenMP_CXX)
+celer_add_ext_library(VecGeom VecGeom::vecgeom)
+if(CELERITAS_USE_CUDA)
+  # VecGeom headers propagate CUDA includes into host compiler code
+  # see https://gitlab.cern.ch/VecGeom/VecGeom/-/merge_requests/1289
+  target_link_libraries(ExtVecGeom INTERFACE Celeritas::ExtDeviceApi)
+endif()
+
+
+if(CELERITAS_BUILTIN_Perfetto)
+  # See /external
+  set(_perfetto_lib celeritas_perfetto)
+endif()
+celer_add_ext_library(Perfetto ${_pefetto_lib})
+
+celeritas_add_interface_library(ExtGeant4Geo)
+if(CELERITAS_USE_Geant4)
+  celeritas_get_g4libs(_cg4_libs geometry)
+  target_link_libraries(ExtGeant4Geo INTERFACE ${_cg4_libs})
+endif()
+
+#----------------------------------------------------------------------------#
+# CODE LIBRARIES
+#----------------------------------------------------------------------------#
+
 # Add individual libraries
 add_subdirectory(corecel)
 add_subdirectory(geocel)
diff --git a/src/accel/CMakeLists.txt b/src/accel/CMakeLists.txt
index 98197b70b5..22b4a448bf 100644
--- a/src/accel/CMakeLists.txt
+++ b/src/accel/CMakeLists.txt
@@ -4,8 +4,16 @@
 #-----------------------------------------------------------------------------#
 
 set(SOURCES)
-set(PRIVATE_DEPS nlohmann_json::nlohmann_json)
-set(PUBLIC_DEPS Celeritas::celeritas Celeritas::corecel)
+set(PRIVATE_DEPS
+  nlohmann_json::nlohmann_json
+  Celeritas::ExtOpenMP # TODO: provide thread utils in corecel
+)
+set(PUBLIC_DEPS
+  Celeritas::BuildFlags
+  Celeritas::corecel
+  Celeritas::geocel
+  Celeritas::celeritas
+)
 
 #-----------------------------------------------------------------------------#
 # Main code
@@ -45,10 +53,6 @@ if(CELERITAS_USE_HepMC3)
   list(APPEND SOURCES HepMC3PrimaryGenerator.cc)
 endif()
 
-if(CELERITAS_USE_OpenMP)
-  list(APPEND PRIVATE_DEPS OpenMP::OpenMP_CXX)
-endif()
-
 celeritas_get_g4libs(_g4_private digits_hits run)
 list(APPEND PRIVATE_DEPS ${_g4_private})
 
diff --git a/src/celeritas/CMakeLists.txt b/src/celeritas/CMakeLists.txt
index 7be5c7bd16..f59a4830f6 100644
--- a/src/celeritas/CMakeLists.txt
+++ b/src/celeritas/CMakeLists.txt
@@ -4,8 +4,29 @@
 #-----------------------------------------------------------------------------#
 
 set(SOURCES)
-set(PRIVATE_DEPS Celeritas::DeviceToolkit nlohmann_json::nlohmann_json)
-set(PUBLIC_DEPS Celeritas::corecel Celeritas::geocel)
+set(PRIVATE_DEPS
+  Celeritas::ExtDeviceApi
+  nlohmann_json::nlohmann_json
+  Celeritas::ExtMPI
+  Celeritas::ExtOpenMP
+)
+set(PUBLIC_DEPS
+  Celeritas::BuildFlags
+  Celeritas::corecel
+  Celeritas::geocel
+)
+
+# Core data has transient includes
+if(CELERITAS_CORE_GEO STREQUAL "VecGeom")
+  # NOTE: use vecgeom directly here since we know it's enabled and it's
+  # necessary to make CudaRdc work downstream
+  set(_core_geo_deps VecGeom::vecgeom)
+elseif(CELERITAS_CORE_GEO STREQUAL "ORANGE")
+  set(_core_geo_deps Celeritas::orange)
+elseif(CELERITAS_CORE_GEO STREQUAL "Geant4")
+  set(_core_geo_deps Celeritas::ExtGeant4Geo)
+endif()
+list(APPEND PUBLIC_DEPS ${_core_geo_deps})
 
 #-----------------------------------------------------------------------------#
 # Main code
@@ -134,8 +155,9 @@ list(APPEND SOURCES
 # Configuration-dependent code
 #-----------------------------------------------------------------------------#
 
-if(CELERITAS_CORE_RNG STREQUAL "CURAND")
-  list(APPEND PUBLIC_DEPS Celeritas::DeviceToolkit)
+if(CELERITAS_CORE_RNG STREQUAL "CURAND"
+    OR CELERITAS_CORE_RNG STREQUAL "hipRAND")
+  list(APPEND PUBLIC_DEPS Celeritas::ExtDeviceApi)
 endif()
 
 if(CELERITAS_USE_CUDA OR CELERITAS_USE_HIP)
@@ -169,16 +191,20 @@ if(CELERITAS_USE_Geant4)
     ext/detail/SensDetInserter.cc
     ext/detail/TouchableUpdaterInterface.cc
   )
+
   celeritas_get_g4libs(_cg4_libs
     global geometry materials processes run physicslists tasking
   )
-  list(APPEND _cg4_libs
-    Celeritas::corecel Celeritas::geocel XercesC::XercesC nlohmann_json::nlohmann_json
-  )
-
   celeritas_add_object_library(celeritas_geant4 ${_cg4_sources})
-  celeritas_target_link_libraries(celeritas_geant4 PRIVATE ${_cg4_libs})
-
+  celeritas_target_link_libraries(celeritas_geant4
+    PRIVATE
+      Celeritas::BuildFlags
+      Celeritas::geocel
+      ${_core_geo_deps} # needed by GeantSd and related
+      XercesC::XercesC
+      nlohmann_json::nlohmann_json
+      ${_cg4_libs}
+  )
   list(APPEND SOURCES $<TARGET_OBJECTS:celeritas_geant4>)
   list(APPEND PRIVATE_DEPS celeritas_geant4)
 endif()
@@ -195,14 +221,6 @@ if(CELERITAS_USE_HepMC3)
   list(APPEND PRIVATE_DEPS celeritas_hepmc)
 endif()
 
-if(CELERITAS_USE_MPI)
-  list(APPEND PRIVATE_DEPS MPI::MPI_CXX)
-endif()
-
-if(CELERITAS_USE_OpenMP)
-  list(APPEND PRIVATE_DEPS OpenMP::OpenMP_CXX)
-endif()
-
 if(CELERITAS_USE_ROOT)
   # Use directory includes because ROOT has trouble with build/install
   # interface dependencies propagated through corecel.
@@ -250,7 +268,7 @@ if(CELERITAS_USE_ROOT)
   )
 
   target_link_libraries(celeritas_root
-    PRIVATE Celeritas::corecel ROOT::Core ROOT::Tree ROOT::RIO
+    PRIVATE Celeritas::BuildFlags ROOT::Core ROOT::Tree ROOT::RIO
   )
 
   # Install the rootmap/pcm files needed for users or downstream apps to use
@@ -269,15 +287,6 @@ if(CELERITAS_USE_ROOT)
   list(APPEND PRIVATE_DEPS celeritas_root)
 endif()
 
-# Core data has transient includes
-if(CELERITAS_CORE_GEO STREQUAL "VecGeom")
-  list(APPEND PUBLIC_DEPS VecGeom::vecgeom)
-elseif(CELERITAS_CORE_GEO STREQUAL "ORANGE")
-  list(APPEND PUBLIC_DEPS Celeritas::orange)
-elseif(CELERITAS_CORE_GEO STREQUAL "Geant4")
- list(APPEND PUBLIC_DEPS ${Geant4_LIBRARIES})
-endif()
-
 #-----------------------------------------------------------------------------#
 # Optional CUDA code
 #-----------------------------------------------------------------------------#
@@ -351,20 +360,6 @@ celeritas_polysource(user/detail/StepGatherAction)
 
 celeritas_add_src_library(celeritas ${SOURCES})
 
-if(NOT CELERITAS_USE_OpenMP
-    AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
-         OR CMAKE_CXX_COMPILER_ID MATCHES "Clang$"))
-  celeritas_target_compile_options(celeritas
-    PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-Wno-unknown-pragmas>"
-  )
-endif()
-if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
-  # Inheriting via dominance is correct behavior
-  celeritas_target_compile_options(celeritas
-    PUBLIC "$<$<COMPILE_LANGUAGE:CXX>:/wd4267$<SEMICOLON>/wd4250>"
-  )
-endif()
-
 celeritas_target_link_libraries(celeritas
   PRIVATE ${PRIVATE_DEPS}
   PUBLIC ${PUBLIC_DEPS}
diff --git a/src/corecel/CMakeLists.txt b/src/corecel/CMakeLists.txt
index 75b92279b7..d30b8808d8 100644
--- a/src/corecel/CMakeLists.txt
+++ b/src/corecel/CMakeLists.txt
@@ -4,8 +4,8 @@
 #-----------------------------------------------------------------------------#
 
 set(SOURCES)
-set(PRIVATE_DEPS nlohmann_json::nlohmann_json)
-set(PUBLIC_DEPS)
+set(PRIVATE_DEPS nlohmann_json::nlohmann_json Celeritas::ExtDeviceApi)
+set(PUBLIC_DEPS Celeritas::BuildFlags)
 
 #----------------------------------------------------------------------------#
 # Configure Files
@@ -192,7 +192,6 @@ list(APPEND SOURCES
   io/Label.cc
   io/LogContextException.cc
   io/LogHandlers.cc
-  io/Logger.cc
   io/LoggerTypes.cc
   io/OutputInterface.cc
   io/OutputRegistry.cc
@@ -206,17 +205,13 @@ list(APPEND SOURCES
   sys/ActionInterface.cc
   sys/ActionRegistry.cc
   sys/ActionRegistryOutput.cc
-  sys/Device.cc
   sys/DeviceIO.json.cc
   sys/Environment.cc
   sys/KernelRegistry.cc
   sys/KernelRegistryIO.json.cc
   sys/MemRegistry.cc
   sys/MemRegistryIO.json.cc
-  sys/MpiCommunicator.cc
-  sys/MultiExceptionHandler.cc
   sys/ScopedMem.cc
-  sys/ScopedMpiInit.cc
   sys/ScopedProfiling.cc
   sys/ScopedSignalHandler.cc
   sys/TypeDemangler.cc
@@ -224,10 +219,52 @@ list(APPEND SOURCES
 )
 
 #-----------------------------------------------------------------------------#
-# Configuration-dependent code/dependencies
+# Configuration-dependent object libraries
+#-----------------------------------------------------------------------------#
+
+# All these classes depend on OpenMP if available
+celeritas_add_object_library(corecel_openmp_obj
+  sys/MultiExceptionHandler.cc
+)
+celeritas_target_link_libraries(corecel_openmp_obj
+  PRIVATE Celeritas::ExtOpenMP Celeritas::BuildFlags
+)
+list(APPEND SOURCES $<TARGET_OBJECTS:corecel_openmp_obj>)
+list(APPEND PRIVATE_DEPS corecel_openmp_obj)
+
+#-----------------------------------------------------------------------------#
+
+# All these classes depend on MPI if available
+celeritas_add_object_library(corecel_mpi_obj
+  io/Logger.cc
+  sys/Device.cc
+  sys/MpiCommunicator.cc
+  sys/ScopedMpiInit.cc
+)
+celeritas_target_link_libraries(corecel_mpi_obj
+  PRIVATE Celeritas::ExtMPI Celeritas::ExtDeviceApi Celeritas::BuildFlags
+)
+list(APPEND SOURCES $<TARGET_OBJECTS:corecel_mpi_obj>)
+list(APPEND PRIVATE_DEPS corecel_mpi_obj)
+
 #-----------------------------------------------------------------------------#
 
-list(APPEND PRIVATE_DEPS Celeritas::DeviceToolkit)
+if(CELERITAS_USE_Perfetto)
+  # Perfetto is required to build these classes
+  celeritas_add_object_library(corecel_perfetto_obj
+    sys/TraceCounter.perfetto.cc
+    sys/TracingSession.cc
+  )
+  celeritas_target_link_libraries(corecel_perfetto_obj
+    PRIVATE Celeritas::ExtPerfetto Celeritas::BuildFlags
+  )
+  list(APPEND SOURCES $<TARGET_OBJECTS:corecel_perfetto_obj>)
+  list(APPEND PRIVATE_DEPS corecel_perfetto_obj)
+endif()
+
+#-----------------------------------------------------------------------------#
+# CUDA/HIP code
+#-----------------------------------------------------------------------------#
 
 if(CELERITAS_USE_CUDA OR CELERITAS_USE_HIP)
   list(APPEND SOURCES
@@ -237,16 +274,13 @@ if(CELERITAS_USE_CUDA OR CELERITAS_USE_HIP)
     sys/detail/AsyncMemoryResource.cu
   )
 endif()
+
 if(CELERITAS_USE_CUDA)
   # Note: we currently assume the CUDA install includes the nvtx header
   list(APPEND SOURCES
     sys/ScopedLimitSaver.cuda.cc
     sys/ScopedProfiling.cuda.cc
   )
-  # NVTX requires `-ldl`
-  # This does not appears to be needed anymore with
-  # CMake 3.26.5 and CUDA sdk 12.6.
-  list(APPEND PRIVATE_DEPS ${CMAKE_DL_LIBS})
 endif()
 
 if(CELERITAS_USE_HIP)
@@ -255,75 +289,14 @@ if(CELERITAS_USE_HIP)
   )
 endif()
 
-if(CELERITAS_USE_MPI)
-  list(APPEND PRIVATE_DEPS MPI::MPI_CXX)
-endif()
-
-if(CELERITAS_USE_OpenMP)
-  list(APPEND PRIVATE_DEPS OpenMP::OpenMP_CXX)
-endif()
-
-if(CELERITAS_USE_Perfetto)
-  list(APPEND PRIVATE_DEPS Celeritas::Perfetto)
-  list(APPEND SOURCES
-    sys/TraceCounter.perfetto.cc
-    sys/TracingSession.cc
-  )
-endif()
-
 #-----------------------------------------------------------------------------#
 # Create library
 #-----------------------------------------------------------------------------#
 
 celeritas_add_src_library(corecel ${SOURCES})
-
-# Require at least C++17
-target_compile_features(corecel PUBLIC cxx_std_17)
-if(CELERITAS_USE_CUDA)
-  target_compile_features(corecel PUBLIC cuda_std_17)
-endif()
-if(WIN32)
-  target_compile_definitions(corecel PUBLIC
-    $<$<COMPILE_LANGUAGE:CXX>:NOMINMAX NOGDI>
-  )
-
-  if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
-    # __cplusplus: use standard-compliant C++ version macro
-    # preprocessor: use standard-compliant preprocessor
-    # EHsc: enable standard C++ exception handling
-    target_compile_options(corecel PUBLIC
-      $<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus /Zc:preprocessor /EHsc>
-    )
-  endif()
-endif()
-if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
-    AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13)
-  # GCC 13 and 14 have *lots* of false positives, and not just on our project
-  # see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107532
-  celeritas_target_compile_options(corecel
-    PUBLIC "$<$<COMPILE_LANGUAGE:CXX>:-Wno-dangling-reference>"
-  )
-endif()
-
-celeritas_target_include_directories(corecel
-  PUBLIC
-    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
-    "$<BUILD_INTERFACE:${CELERITAS_HEADER_CONFIG_DIRECTORY}>"
-    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
-)
 celeritas_target_link_libraries(corecel
   PRIVATE ${PRIVATE_DEPS}
   PUBLIC ${PUBLIC_DEPS}
 )
 
 #-----------------------------------------------------------------------------#
-# Create library
-#-----------------------------------------------------------------------------#
-
-celeritas_add_src_library(corecel_mpi INTERFACE)
-
-if(CELERITAS_USE_MPI)
-  target_link_libraries(corecel_mpi INTERFACE MPI::MPI_CXX)
-endif()
-
-#-----------------------------------------------------------------------------#
diff --git a/src/corecel/sys/Device.cc b/src/corecel/sys/Device.cc
index 35fd557a4e..a81d3d8dea 100644
--- a/src/corecel/sys/Device.cc
+++ b/src/corecel/sys/Device.cc
@@ -14,7 +14,6 @@
 #include "corecel/Config.hh"
 
 #include "corecel/Macros.hh"
-#include "corecel/math/Algorithms.hh"
 #if CELERITAS_USE_OPENMP
 #    include <omp.h>
 #endif
@@ -22,6 +21,7 @@
 #include "corecel/DeviceRuntimeApi.hh"
 
 #include "corecel/Assert.hh"
+#include "corecel/Macros.hh"
 #include "corecel/io/Logger.hh"
 #include "corecel/io/ScopedTimeLog.hh"
 
@@ -97,8 +97,8 @@ int Device::num_devices()
     static int const result = [] {
         if (!CELER_USE_DEVICE)
         {
-            CELER_LOG(debug) << "Disabling GPU support since CUDA and HIP are "
-                                "disabled";
+            CELER_LOG(debug)
+                << R"(Disabling GPU support since CUDA and HIP are disabled)";
             return 0;
         }
 
@@ -115,8 +115,7 @@ int Device::num_devices()
         if (result == 0)
         {
             CELER_LOG(warning)
-                << "Disabling GPU support since no CUDA devices "
-                   "are present";
+                << R"(Disabling GPU support since no CUDA devices are present)";
         }
 
         CELER_ENSURE(result >= 0);
@@ -345,9 +344,8 @@ void activate_device(Device&& device)
     static std::mutex m;
     std::lock_guard<std::mutex> scoped_lock{m};
     Device& d = global_device();
-    CELER_VALIDATE(!d,
-                   << "celeritas::activate_device may be called only once per "
-                      "application");
+    CELER_VALIDATE(
+        !d, << R"(activate_device may be called only once per application)");
 
     if (!device)
         return;
@@ -453,8 +451,8 @@ void set_cuda_stack_size(int limit)
     CELER_EXPECT(limit > 0);
     if (!celeritas::device())
     {
-        CELER_LOG(warning) << "Ignoring call to set_cuda_stack_size: no "
-                              "device is available";
+        CELER_LOG(warning)
+            << R"(Ignoring call to set_cuda_stack_size: no device is available)";
         return;
     }
     if constexpr (CELERITAS_USE_CUDA)
@@ -478,8 +476,8 @@ void set_cuda_heap_size(int limit)
     CELER_EXPECT(limit > 0);
     if (!celeritas::device())
     {
-        CELER_LOG(warning) << "Ignoring call to set_cuda_stack_size: no "
-                              "device is available";
+        CELER_LOG(warning)
+            << R"(Ignoring call to set_cuda_heap_size: no device is available)";
         return;
     }
     if constexpr (CELERITAS_USE_CUDA)
diff --git a/src/geocel/CMakeLists.txt b/src/geocel/CMakeLists.txt
index 710c31aa07..6a54e3c3e5 100644
--- a/src/geocel/CMakeLists.txt
+++ b/src/geocel/CMakeLists.txt
@@ -29,8 +29,9 @@ if(CELERITAS_USE_PNG)
   celeritas_add_object_library(geocel_png
     rasterize/ImageWriter.libpng.cc
   )
-  celeritas_target_link_libraries(geocel_png PRIVATE PNG::PNG Celeritas::corecel)
-
+  celeritas_target_link_libraries(geocel_png
+    PRIVATE PNG::PNG Celeritas::BuildFlags
+  )
   list(APPEND SOURCES $<TARGET_OBJECTS:geocel_png>)
   list(APPEND PRIVATE_DEPS geocel_png)
 endif()
@@ -52,16 +53,16 @@ if(CELERITAS_USE_Geant4)
     g4/detail/GeantGeoNavCollection.cc
   )
 
-  celeritas_get_g4libs(_cg4_geo_libs geometry)
   celeritas_get_g4libs(_cg4_libs global geometry materials particles
     persistency intercoms)
-  list(APPEND _cg4_libs Celeritas::corecel XercesC::XercesC nlohmann_json::nlohmann_json)
   celeritas_target_link_libraries(geocel_geant4
-    PRIVATE ${_cg4_libs} ${_cg4_geo_libs})
+    PRIVATE
+      ${_cg4_libs} XercesC::XercesC nlohmann_json::nlohmann_json
+      Celeritas::BuildFlags
+  )
 
   list(APPEND SOURCES $<TARGET_OBJECTS:geocel_geant4>)
   list(APPEND PRIVATE_DEPS geocel_geant4)
-  list(APPEND PUBLIC_DEPS ${_cg4_geo_libs})
 endif()
 
 if(CELERITAS_USE_VecGeom)
@@ -76,18 +77,18 @@ if(CELERITAS_USE_VecGeom)
     list(APPEND PRIVATE_DEPS VecGeom::vgdml)
   endif()
   if(CELERITAS_USE_CUDA)
-    # This needs to be public because its might be needed
-    # to resolve the symbols generate by the `nvcc -dlink` of
-    # one of the executable.
     list(APPEND SOURCES
       vg/RaytraceImager.cu
       vg/detail/VecgeomSetup.cu
     )
+    # VecGeom CUDA is required in this case; those libraries are automatically
+    # configured via the CudaRdc linking
   elseif(VecGeom_CUDA_FOUND)
-    # VecGeom is built with CUDA but Celeritas is not
-    list(APPEND PRIVATE_DEPS VecGeom::vecgeomcuda)
+    # VecGeom is built with CUDA but Celeritas is not: need to fill missing
+    # symbols with the VecGeom "final" library
+    list(APPEND PUBLIC_DEPS VecGeom::vecgeomcuda)
   endif()
-  list(APPEND PUBLIC_DEPS VecGeom::vecgeom)
+  list(APPEND PRIVATE_DEPS VecGeom::vecgeom)
 endif()
 
 #-----------------------------------------------------------------------------#
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 0194e35fa4..231e79afba 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -12,12 +12,6 @@ include(CeleritasAddTest)
 file(TO_CMAKE_PATH "${PROJECT_SOURCE_DIR}" CELERITAS_SOURCE_DIR)
 configure_file(celeritas_test_config.h.in celeritas_test_config.h @ONLY)
 
-if(CELERITAS_USE_MPI)
-  set(_mpi_libs MPI::MPI_CXX)
-else()
-  set(_mpi_libs)
-endif()
-
 set(_harness_sources
   Test.cc
   testdetail/JsonComparer.json.cc
@@ -29,7 +23,7 @@ celeritas_add_test_library(testcel_harness ${_harness_sources})
 target_compile_features(testcel_harness PUBLIC cxx_std_17)
 celeritas_target_link_libraries(testcel_harness
   PUBLIC Celeritas::corecel GTest::gtest
-  PRIVATE nlohmann_json::nlohmann_json ${_mpi_libs}
+  PRIVATE nlohmann_json::nlohmann_json Celeritas::ExtMPI
 )
 celeritas_target_include_directories(testcel_harness
   PUBLIC
diff --git a/test/celeritas/CMakeLists.txt b/test/celeritas/CMakeLists.txt
index e087402bc8..763f70e884 100644
--- a/test/celeritas/CMakeLists.txt
+++ b/test/celeritas/CMakeLists.txt
@@ -20,11 +20,9 @@ endif()
 
 if(NOT CELERITAS_USE_Geant4)
   set(_needs_geant4 DISABLE)
-  set(_g4_geo_libs)
 else()
   set(_optional_geant4_env ENVIRONMENT "${CELER_G4ENV}")
   set(_needs_geant4 ${_optional_geant4_env})
-  celeritas_get_g4libs(_g4_geo_libs geometry)
 endif()
 
 if(NOT CELERITAS_USE_HepMC3)
@@ -46,7 +44,7 @@ endif()
 
 set(_all_geo_libs
   # Geometry includes
-  Celeritas::orange ${VecGeom_LIBRARIES} ${_g4_geo_libs}
+  Celeritas::orange ${VecGeom_LIBRARIES} Celeritas::ExtGeant4Geo
   # Test harness instantiations
   testcel_geocel testcel_orange
 )
@@ -101,7 +99,7 @@ celeritas_target_link_libraries(testcel_celeritas
     testcel_harness Celeritas::celeritas
   PRIVATE
     testcel_core testcel_orange testcel_geocel
-    nlohmann_json::nlohmann_json ${_g4_geo_libs}
+    nlohmann_json::nlohmann_json Celeritas::ExtGeant4Geo
 )
 
 if(CELERITAS_USE_Geant4)
@@ -114,7 +112,7 @@ if(CELERITAS_USE_Geant4)
   celeritas_target_link_libraries(testcel_celeritas_g4
     PUBLIC
       testcel_celeritas testcel_harness Celeritas::celeritas
-      ${_g4_mat_libs} ${_g4_geo_libs}
+      ${_g4_mat_libs} Celeritas::ExtGeant4Geo
   )
 endif()
 
diff --git a/test/geocel/CMakeLists.txt b/test/geocel/CMakeLists.txt
index 57c85f15ec..f9cc147926 100644
--- a/test/geocel/CMakeLists.txt
+++ b/test/geocel/CMakeLists.txt
@@ -6,7 +6,6 @@
 if(NOT CELERITAS_USE_Geant4)
   set(_needs_geant4 DISABLE)
 else()
-  celeritas_get_g4libs(_g4_geo_libs geometry)
   celeritas_get_g4libs(_g4_global_libs global)
 endif()
 
@@ -31,6 +30,7 @@ set(SOURCES
   GeoTests.cc
 )
 
+set(PRIVATE_DEPS ${testcel_core} Celeritas::ExtGeant4Geo)
 if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double")
   list(APPEND SOURCES
     g4/GeantGeoTestBase.cc
@@ -40,15 +40,14 @@ if(CELERITAS_USE_VecGeom)
   list(APPEND SOURCES
     vg/VecgeomTestBase.cc
   )
+  list(APPEND PRIVATE_DEPS Celeritas::ExtVecGeom)
 endif()
 
 celeritas_add_test_library(testcel_geocel ${SOURCES})
 
 celeritas_target_link_libraries(testcel_geocel
-  PUBLIC
-    testcel_harness Celeritas::geocel
-  PRIVATE
-    testcel_core
+  PUBLIC testcel_harness Celeritas::geocel
+  PRIVATE ${PRIVATE_DEPS}
 )
 
 #-----------------------------------------------------------------------------#
@@ -103,7 +102,8 @@ if(CELERITAS_USE_VecGeom)
     set(_vecgeom_tests DISABLE)
   endif()
   celeritas_add_device_test(vg/Vecgeom
-    LINK_LIBRARIES VecGeom::vecgeom ${_g4_geo_libs} nlohmann_json::nlohmann_json
+    LINK_LIBRARIES
+      VecGeom::vecgeom Celeritas::ExtGeant4Geo nlohmann_json::nlohmann_json
     FILTER ${_vecgeom_tests}
     ${_fixme_cgs}
   )
@@ -112,7 +112,8 @@ endif()
 #-----------------------------------------------------------------------------#
 # Geant4
 
-celeritas_add_test(GeantGdmlLoader.test.cc ${_needs_geant4} LINK_LIBRARIES ${_g4_geo_libs})
+list(APPEND CELERITASTEST_LINK_LIBRARIES Celeritas::ExtGeant4Geo)
+celeritas_add_test(GeantGdmlLoader.test.cc ${_needs_geant4})
 
 if(CELERITAS_USE_Geant4)
   celeritas_add_test(ScopedGeantLogger.test.cc LINK_LIBRARIES ${_g4_global_libs})
@@ -121,10 +122,11 @@ endif()
 if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double")
   # These all require the Geant4 geometry wrappers which only are built when
   # double-precision is enabled
-  celeritas_add_test(g4/GeantGeo.test.cc LINK_LIBRARIES ${_g4_geo_libs})
-  celeritas_add_test(g4/VisitVolumes.test.cc LINK_LIBRARIES ${_g4_geo_libs})
-  celeritas_add_test(GeantGeoUtils.test.cc LINK_LIBRARIES ${_g4_geo_libs})
+  celeritas_add_test(g4/GeantGeo.test.cc)
+  celeritas_add_test(g4/VisitVolumes.test.cc)
+  celeritas_add_test(GeantGeoUtils.test.cc)
 endif()
+list(POP_BACK CELERITASTEST_LINK_LIBRARIES)
 
 #-----------------------------------------------------------------------------#
 # Rasterization
diff --git a/test/orange/CMakeLists.txt b/test/orange/CMakeLists.txt
index 0acfb0d0e2..479a7891cf 100644
--- a/test/orange/CMakeLists.txt
+++ b/test/orange/CMakeLists.txt
@@ -21,10 +21,6 @@ celeritas_target_link_libraries(testcel_orange
 # VARIABLES
 #-----------------------------------------------------------------------------#
 
-if(CELERITAS_USE_Geant4)
-  celeritas_get_g4libs(_g4_geo_libs geometry)
-endif()
-
 if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double")
   set(_use_g4org TRUE)
   set(_needs_g4org)
@@ -148,11 +144,11 @@ celeritas_add_test(univ/detail/SenseCalculator.test.cc)
 if(_use_g4org)
   celeritas_add_test(g4org/Converter.test.cc)
   celeritas_add_test(g4org/SolidConverter.test.cc
-    LINK_LIBRARIES ${_g4_geo_libs} Celeritas::celeritas)
+    LINK_LIBRARIES Celeritas::ExtGeant4Geo Celeritas::celeritas)
   celeritas_add_test(g4org/PhysicalVolumeConverter.test.cc)
   celeritas_add_test(g4org/ProtoConstructor.test.cc)
   celeritas_add_test(g4org/Transformer.test.cc
-    LINK_LIBRARIES ${_g4_geo_libs})
+    LINK_LIBRARIES Celeritas::ExtGeant4Geo)
 endif()
 
 #-----------------------------------------------------------------------------#


Reviews:
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:55:17Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T17:57:19Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:57:42Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T17:59:34Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T18:03:06Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T18:04:53Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T18:10:07Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T20:12:14Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:33:56Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T20:36:09Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:42:30Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:38:11Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T12:53:00Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:42:22Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T15:45:10Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T15:48:33Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:13:21Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:14:22Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:40:06Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:46:58Z
user: login: pcanal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:48:37Z
user: login: pcanal
body: LGTM.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:52:13Z
user: login: sethrj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:54:50Z

Review comments:
diff_hunk: @@ -497,6 +497,25 @@ if(CELERITAS_BUILD_TESTS)
   celeritas_find_or_builtin_package(GTest 1.10)
 endif()
 
+#----------------------------------------------------------------------------#
+# System property: availability of sincospi
+if(NOT DEFINED CELERITAS_SINCOSPI_PREFIX)
path: CMakeLists.txt
user: login: pcanal
body: For clarify I would have set the full name of the function instead of just the prefix.  (Albeit technically the existing code could be abstract out to test/check another symbol with the same pattern).  Either way minor issue.
created_at: 2025-03-17T17:55:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -497,6 +497,25 @@ if(CELERITAS_BUILD_TESTS)
   celeritas_find_or_builtin_package(GTest 1.10)
 endif()
 
+#----------------------------------------------------------------------------#
+# System property: availability of sincospi
+if(NOT DEFINED CELERITAS_SINCOSPI_PREFIX)
path: CMakeLists.txt
user: login: sethrj
body: It's not just one function though, it's several... #1364
created_at: 2025-03-17T17:57:18Z
author_association: MEMBER
diff_hunk: @@ -112,20 +122,28 @@ forward directly to CMake or alternatively to CudaRdc.
 
   See ``target_compile_options`` for additional detail.
 
-.. command:: celeritas_configure_file
 
-  Configure to the build "include" directory for later installation::
+Helper functions
+^^^^^^^^^^^^^^^^
 
-    celeritas_configure_file(<input> <output> [ARGS...])
+.. command:: celeritas_get_cuda_source_args
 
-  The ``<input>`` must be a relative path to the current source directory, and
-  the ``<output>` path is configured to the project build "include" directory.
+  Get a list of all source files in the argument that are CUDA language.
 
-.. command:: celeritas_polysource_append
+    celeritas_get_cuda_source_args(<var> [<source> ...])
 
-  Add C++ and CUDA/HIP source files based on the enabled options.
+.. command:: celeritas_add_library
path: cmake/CeleritasLibraryUtils.cmake
user: login: pcanal
body: odd placement.  Wouldn't be better next to `celeritas_add_interface_library`?
created_at: 2025-03-17T17:57:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -112,20 +122,28 @@ forward directly to CMake or alternatively to CudaRdc.
 
   See ``target_compile_options`` for additional detail.
 
-.. command:: celeritas_configure_file
 
-  Configure to the build "include" directory for later installation::
+Helper functions
+^^^^^^^^^^^^^^^^
 
-    celeritas_configure_file(<input> <output> [ARGS...])
+.. command:: celeritas_get_cuda_source_args
 
-  The ``<input>`` must be a relative path to the current source directory, and
-  the ``<output>` path is configured to the project build "include" directory.
+  Get a list of all source files in the argument that are CUDA language.
 
-.. command:: celeritas_polysource_append
+    celeritas_get_cuda_source_args(<var> [<source> ...])
 
-  Add C++ and CUDA/HIP source files based on the enabled options.
+.. command:: celeritas_add_library
path: cmake/CeleritasLibraryUtils.cmake
user: login: sethrj
body: I thought this function was only used internal to this file but looks like it's used elsewhere. I can move it around.
created_at: 2025-03-17T17:59:34Z
author_association: MEMBER
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: pcanal
body: humm .. that should not be need :(
created_at: 2025-03-17T18:03:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: pcanal
body: i.e. With the PR we recently merged, it should have installed the Celeritas version and it should have been preferred ... 
created_at: 2025-03-17T18:04:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -497,6 +497,25 @@ if(CELERITAS_BUILD_TESTS)
   celeritas_find_or_builtin_package(GTest 1.10)
 endif()
 
+#----------------------------------------------------------------------------#
+# System property: availability of sincospi
path: CMakeLists.txt
user: login: pcanal
body: ```suggestion
# System property: availability of sincospi
# The same prefix used for sincospi applies to the whole family of function (sinpi, cospi, sincos).
```
created_at: 2025-03-17T18:10:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: sethrj
body: Yeah I was flailing ... I thought that maybe if Celeritas loads RdcUtils, then an old VecGeom loads RdcUtils, then we tried to load Celeritas RdcUtils again, it wouldn't work?

Also ... I just realized ... neither of the config files actually include RdcUtils, they just modify the paths. So when we do `include(CudaRdcUtils)` it's based on what config file last did `list(APPEND CMAKE_MODULE_PATH`... which will be Celeritas if you explicitly load vecgeom first, or VecGeom if you let Celeritas loaded it. The versioning guard won't affect that :(
created_at: 2025-03-17T20:12:14Z
author_association: MEMBER
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: pcanal
body: > VecGeom if you let Celeritas loaded it.

We could/should fix that.
created_at: 2025-03-17T20:33:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: sethrj
body: We'd have to unconditionally include CudaRdcUtils from both Config files, right?
created_at: 2025-03-17T20:36:09Z
author_association: MEMBER
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: pcanal
body: Yes, I think so.
created_at: 2025-03-17T20:42:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,9 @@ if(NOT CELERITAS_USE_Geant4)
     "to be enabled in Celeritas")
 endif()
 
-include(CudaRdcUtils)
+# Use Celeritas CUDA RDC macros to correctly link libraries using VecGeom
+# NOTE: until vecgeom/g4vg get updated, we must use the Celeritas load path
+include("${Celeritas_DIR}/CudaRdcUtils.cmake")
path: example/accel/CMakeLists.txt
user: login: sethrj
body: I guess we should've been doing this all along 🤔 
created_at: 2025-03-17T21:38:10Z
author_association: MEMBER
diff_hunk: @@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
path: CMakeLists.txt
user: login: pcanal
body: This was made 'unnecessary' because the optionality of the install of the `CudaRdcUtils` was inadvertently (!?) removed in https://github.com/celeritas-project/celeritas/pull/1674 and in particular [`f6fc29f` (#1674)](https://github.com/celeritas-project/celeritas/pull/1674/commits/f6fc29fa99c2d6bbe465d9ef5f2050f4a8aacc9c).  

That commit seems to address the issue "Always install RdcUtils since the build fails without it " but it is not clear that this was the best solution ... and the very least the commit log could use an extension to explain why installing  an older/obsolete version is useful.
created_at: 2025-03-18T12:53:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
path: CMakeLists.txt
user: login: sethrj
body: It was an expedient to fix the build. I'll update the PR notes. I'd like to get this in to enable `celeritas_add_library` downstream in our examples and tag an `rc`; then could you create a new PR that finesses the installation and reverts this chunk and https://github.com/celeritas-project/celeritas/pull/1674/commits/f6fc29fa99c2d6bbe465d9ef5f2050f4a8aacc9c if you can get everything working without it?
created_at: 2025-03-18T14:42:22Z
author_association: MEMBER
diff_hunk: @@ -16,7 +16,7 @@ if(NOT CELERITAS_USE_Geant4)
 endif()
 
 add_executable(simple-offload simple-offload.cc)
-cuda_rdc_target_link_libraries(simple-offload
+celeritas_target_link_libraries(simple-offload
path: example/accel/CMakeLists.txt
user: login: pcanal
body: This is a notable strategic change that deserve a mention/justification in the PR synopsis (and maybe the git log).
created_at: 2025-03-18T15:45:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
path: CMakeLists.txt
user: login: pcanal
body: Since we agreed to always distribute the local version, there is no longer a need to revert.  But whether we use below:
```
include(CudaRdcUtils OPTIONAL)
include("${_LOCAL_RDCUTILS_FILENAME}")
```
or just
```
include("${_LOCAL_RDCUTILS_FILENAME}")
```
depends on whether we choose to trust the `find_package` to already include their own version (i.e. in case theirs is newer).

created_at: 2025-03-18T15:48:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
path: CMakeLists.txt
user: login: sethrj
body: We haven't release any versions of VecGeom nor G4VG yet that include RdcUtils in their Config file. Future VG and G4VG should distribute and include it (and their inclusion will be new enough not to override extant newer versions). Therefore in the future a `find_package` alone will be enough to load the newest RdcUtils among all the dependencies. Present `find_package` is unnecessary because we know no present version is newer than ours.

Does that sound right?
created_at: 2025-03-18T16:13:21Z
author_association: MEMBER
diff_hunk: @@ -16,7 +16,7 @@ if(NOT CELERITAS_USE_Geant4)
 endif()
 
 add_executable(simple-offload simple-offload.cc)
-cuda_rdc_target_link_libraries(simple-offload
+celeritas_target_link_libraries(simple-offload
path: example/accel/CMakeLists.txt
user: login: sethrj
body: It's something of a spinoff from the "documentation needs updating" 😅 You're right that it might be better to move to a separate quick follow-on PR.
created_at: 2025-03-18T16:14:22Z
author_association: MEMBER
diff_hunk: @@ -16,7 +16,7 @@ if(NOT CELERITAS_USE_Geant4)
 endif()
 
 add_executable(simple-offload simple-offload.cc)
-cuda_rdc_target_link_libraries(simple-offload
+celeritas_target_link_libraries(simple-offload
path: example/accel/CMakeLists.txt
user: login: sethrj
body: OK moved to #1678 
created_at: 2025-03-18T16:40:06Z
author_association: MEMBER
diff_hunk: @@ -512,88 +532,18 @@ endif()
 # RDC SUPPORT FOR VECGEOM+CUDA
 #----------------------------------------------------------------------------#
 
-# Load RDC utils using CMAKE_MODULE_PATH
-include(CudaRdcUtils OPTIONAL)
-
-# Load our local copy if it's newer
-if(NOT CUDA_RDC_VERSION)
-   set(_loaded_CUDA_RDC_VERSION 0)
-else()
-   set(_loaded_CUDA_RDC_VERSION ${CUDA_RDC_VERSION})
-endif()
path: CMakeLists.txt
user: login: pcanal
body: That sounds about right.  (On a side note, this PR is changing `CudaRdcUtils.cmake` (minorly so it does not actually matter) but does not increase the version number ... at the same time `VecGeom` has a PR that is also changing `CudaRdcUtils.cmake` (with a slight behavior change) and should update the version number too) :( )
created_at: 2025-03-18T16:46:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,9 +22,9 @@ jobs:
       fail-fast: false
       matrix:
         special: [null]
-        geometry: ["orange", "vecgeom"]
+        geometry: ["vecgeom"]
path: .github/workflows/build-docker.yml
user: login: pcanal
body: Reminder: revert.
created_at: 2025-03-18T16:48:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,9 +22,9 @@ jobs:
       fail-fast: false
       matrix:
         special: [null]
-        geometry: ["orange", "vecgeom"]
+        geometry: ["vecgeom"]
path: .github/workflows/build-docker.yml
user: login: sethrj
body: Yep when I do the "revertme " I delete the "all" job so it's impossible to merge till we restore it :)
created_at: 2025-03-18T16:54:50Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-15T23:54:07Z
body: ## Test summary
  591 files    884 suites   3m 56s :stopwatch:
1 703 tests 1 695 :white_check_mark:  8 :zzz: 0 :x:
3 446 runs  3 432 :white_check_mark: 14 :zzz: 0 :x:

Results for commit 7b60a3c8.

[test-results]:data:application/gzip;base64,H4sIACmm2WcC/03MyQ6DIBSF4VcxrLsQLjL0ZRpkSEhVGoZV03cvWKUuz3eS/42cX2xC92GS+DagVHzepxC0TlOiyj5sFQiwCvXM7cZ8hHM+UtG6GZPT357+1TIdnPJLhbGDjTHEQ2LZWhUoZcc6o0CBdPo1MT3h0tz3NanDuvpcB+IzGxVoYbUGTuxMrJTGKkYkxkZNzAmQFAuOPl/n6c74DgEAAA==

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: sethrj
created_at: 2025-03-17T12:47:23Z
body: @pcanal I think our CUDA dev system is being slow, but on wildstyle the develop build (CUDA and vecgeom and unit tests) takes 15s to configure (3.8M lines of cmake trace output), and with this branch it takes 30s (7.5M lines of cmake trace output) even though there should be actually fewer libraries linked than before. Maybe after you review this and the MPI PR you could look and see if there's low-hanging fruit for optimization?
author_association: MEMBER
user: login: sethrj
created_at: 2025-03-17T13:24:22Z
body: Also this is killing me... https://github.com/celeritas-project/celeritas/actions/runs/13899686495/job/38888586851
author_association: MEMBER