Skip to content

Multi-Backend Install Layout Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Refactor the CMake build system so a single build produces CPU + GPU backend variants, installing into bin/<backend>/ directories for forge-studio compatibility.

Architecture: Replace the current single-library/single-executable build with a forge_add_backend() CMake function that stamps out ForgeCommon_<backend> libraries and per-backend executables. Backend-specific compile definitions move from global add_definitions() to per-target target_compile_definitions(). Autodetection logic determines which backends to build.

Tech Stack: CMake 3.17+, C++17

Spec: docs/superpowers/specs/2026-03-25-multi-backend-install-design.md


Task 1: Create cmake/ForgeBackend.cmake with the forge_add_backend() function

This is the core new file. It defines a function that creates a library + executables + install rules for one backend.

Files: - Create: cmake/ForgeBackend.cmake

  • [ ] Step 1: Write the function skeleton

Create cmake/ForgeBackend.cmake with the following content:

# cmake/ForgeBackend.cmake
# Stamps out ForgeCommon_<backend>, app executables, and install rules
# for a single compute backend (cpu, metal, cuda).
#
# Usage:
#   forge_add_backend(<name>
#       SOURCES <source-files>...
#       [DEFINITIONS <compile-defs>...]
#       [EXTRA_LIBS <link-libraries>...]
#       [DEPENDS <target-dependencies>...]
#       [OBJ_C_SOURCES <objc-source-files>...])

include_guard(GLOBAL)

# The list of app source files and their target base names.
# Each entry is "target_name;relative_source_path" (relative to CMAKE_SOURCE_DIR).
set(_FORGE_APP_TARGETS
    "forgeSense;apps/forgeSense.cpp"
    "forgePcSense;apps/forgePcSense.cpp"
    "forgePcSenseTimeSeg;apps/forgePcSenseTimeSeg.cpp"
    "oscillateRecon;apps/lowRank/oscillateRecon.cpp"
    "gridCoilImages;apps/reconSupport/gridCoilImages.cpp"
)

# oscillateRecon has extra source files beyond its main .cpp
set(_FORGE_OSCILLATE_EXTRA_SOURCES
    "${CMAKE_SOURCE_DIR}/apps/lowRank/R_lowRank.cpp"
    "${CMAKE_SOURCE_DIR}/apps/lowRank/LRobj.cpp"
)

# MPI app targets: "target_name;source1;source2;..."
set(_FORGE_MPI_TARGETS
    "forgeSenseMPI;apps/MPI/forgeSenseMPI.cpp"
    "forgePcSenseMPI;apps/MPI/mpipcSENSE.cpp;apps/MPI/forgePcSenseMPI.cpp"
    "forgePcSenseMPI_TS;apps/MPI/mpipcSENSETimeSeg.cpp;apps/MPI/forgePcSenseMPI_TS.cpp"
)

function(forge_add_backend BACKEND_NAME)
    # Parse arguments
    cmake_parse_arguments(ARG "" "" "SOURCES;DEFINITIONS;EXTRA_LIBS;DEPENDS;OBJ_C_SOURCES" ${ARGN})

    set(LIB_TARGET "ForgeCommon_${BACKEND_NAME}")

    # ── Library ────────────────────────────────────────────────────────────
    add_library(${LIB_TARGET} ${ARG_SOURCES})

    target_include_directories(${LIB_TARGET} PUBLIC
        ${PROJECT_SOURCE_DIR}/forge
        ${CMAKE_BINARY_DIR}  # for generated Version.h
    )

    # Per-target compile definitions (replaces global add_definitions)
    if(ARG_DEFINITIONS)
        target_compile_definitions(${LIB_TARGET} PUBLIC ${ARG_DEFINITIONS})
    endif()

    # Link base libs + any backend-specific extras
    target_link_libraries(${LIB_TARGET} PUBLIC ${LIBS})
    if(ARG_EXTRA_LIBS)
        target_link_libraries(${LIB_TARGET} PUBLIC ${ARG_EXTRA_LIBS})
    endif()

    # ObjC ARC flags for Metal .mm sources
    if(ARG_OBJ_C_SOURCES)
        set_source_files_properties(${ARG_OBJ_C_SOURCES}
            PROPERTIES COMPILE_FLAGS "-fobjc-arc")
    endif()

    # Target dependencies (e.g. MetalShaders)
    if(ARG_DEPENDS)
        add_dependencies(${LIB_TARGET} ${ARG_DEPENDS})
    endif()

    set_target_properties(${LIB_TARGET} PROPERTIES
        LINK_DEPENDS_NO_SHARED true
        VERSION   ${PROJECT_VERSION}
        SOVERSION ${PROJECT_VERSION_MAJOR}
    )

    # RPATH: bin/<backend>/ is two levels below lib/
    set_target_properties(${LIB_TARGET} PROPERTIES
        INSTALL_RPATH "$<IF:$<PLATFORM_ID:Darwin>,@loader_path/../../lib,$ORIGIN/../../lib>"
    )

    install(TARGETS ${LIB_TARGET} LIBRARY DESTINATION lib)

    # ── App executables ────────────────────────────────────────────────────
    foreach(ENTRY IN LISTS _FORGE_APP_TARGETS)
        list(GET ENTRY 0 BASE_NAME)
        list(GET ENTRY 1 SOURCE_REL)

        set(EXE_TARGET "${BASE_NAME}_${BACKEND_NAME}")
        set(EXE_SOURCES "${CMAKE_SOURCE_DIR}/${SOURCE_REL}")

        # oscillateRecon has extra sources
        if(BASE_NAME STREQUAL "oscillateRecon")
            list(APPEND EXE_SOURCES ${_FORGE_OSCILLATE_EXTRA_SOURCES})
        endif()

        add_executable(${EXE_TARGET} ${EXE_SOURCES})
        target_link_libraries(${EXE_TARGET} ${LIB_TARGET})

        # Include directories for app sources that use relative includes
        target_include_directories(${EXE_TARGET} PRIVATE
            ${CMAKE_SOURCE_DIR}/Support
            ${CMAKE_SOURCE_DIR}/apps)

        # Install with the original binary name (strip _<backend> suffix)
        set_target_properties(${EXE_TARGET} PROPERTIES
            OUTPUT_NAME ${BASE_NAME}
            INSTALL_RPATH "$<IF:$<PLATFORM_ID:Darwin>,@loader_path/../../lib,$ORIGIN/../../lib>"
        )

        install(TARGETS ${EXE_TARGET}
            DESTINATION bin/${BACKEND_NAME})
    endforeach()

    # ── MPI executables (optional) ──────────────────────────────────��──────
    if(MPISupport AND MPI_FOUND)
        foreach(ENTRY IN LISTS _FORGE_MPI_TARGETS)
            list(GET ENTRY 0 BASE_NAME)
            # Remaining items are source paths
            list(LENGTH ENTRY ENTRY_LEN)
            math(EXPR LAST_IDX "${ENTRY_LEN} - 1")
            set(MPI_SOURCES "")
            foreach(IDX RANGE 1 ${LAST_IDX})
                list(GET ENTRY ${IDX} SRC_REL)
                list(APPEND MPI_SOURCES "${CMAKE_SOURCE_DIR}/${SRC_REL}")
            endforeach()

            set(MPI_TARGET "${BASE_NAME}_${BACKEND_NAME}")
            add_executable(${MPI_TARGET} ${MPI_SOURCES})
            target_link_libraries(${MPI_TARGET} MPI::MPI_CXX ${LIB_TARGET})
            target_compile_definitions(${MPI_TARGET} PRIVATE ForgeMPI)

            # Include directories for MPI app sources
            target_include_directories(${MPI_TARGET} PRIVATE
                ${CMAKE_SOURCE_DIR}/Support
                ${CMAKE_SOURCE_DIR}/apps)

            set_target_properties(${MPI_TARGET} PROPERTIES
                OUTPUT_NAME ${BASE_NAME}
                INSTALL_RPATH "$<IF:$<PLATFORM_ID:Darwin>,@loader_path/../../lib,$ORIGIN/../../lib>"
            )

            install(TARGETS ${MPI_TARGET}
                DESTINATION bin/${BACKEND_NAME})
        endforeach()
    endif()

    # ── Export target name for test linking ─────────────────────────────────
    set("FORGE_LIB_${BACKEND_NAME}" "${LIB_TARGET}" PARENT_SCOPE)
endfunction()
  • [ ] Step 2: Commit
git add cmake/ForgeBackend.cmake
git commit -m "build: add forge_add_backend() CMake function

Stamps out ForgeCommon_<backend> library, app executables, and install
rules for a single compute backend. Supports per-target compile
definitions, optional ObjC ARC sources, MPI variants, and correct
RPATH for bin/<backend>/ layout."

Task 2: Refactor forge/CMakeLists.txt to export source lists instead of building targets

Currently forge/CMakeLists.txt builds ForgeCommon directly. It needs to instead export the source file lists so the root CMakeLists.txt can pass them to forge_add_backend().

Files: - Modify: forge/CMakeLists.txt (full rewrite)

  • [ ] Step 1: Replace forge/CMakeLists.txt

Replace the entire content of forge/CMakeLists.txt with:

# forge/CMakeLists.txt
# Defines source file lists for ForgeCommon.
# The actual library targets are created by forge_add_backend() in the root CMakeLists.txt.

set(FORGE_BASE_SOURCES
    # Penalties
    ${PROJECT_SOURCE_DIR}/forge/Penalties/Robject.cpp
    ${PROJECT_SOURCE_DIR}/forge/Penalties/QuadPenalty.cpp
    ${PROJECT_SOURCE_DIR}/forge/Penalties/TVPenalty.cpp
    # Solvers
    ${PROJECT_SOURCE_DIR}/forge/Solvers/reconSolve.cpp
    # FFT
    ${PROJECT_SOURCE_DIR}/forge/FFT/fftCPU.cpp
    ${PROJECT_SOURCE_DIR}/forge/FFT/ftCpu.cpp
    ${PROJECT_SOURCE_DIR}/forge/FFT/ftCpuWithGrads.cpp
    ${PROJECT_SOURCE_DIR}/forge/FFT/fftAccelerate.cpp
    # Operators
    ${PROJECT_SOURCE_DIR}/forge/Operators/Gdft.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/GdftR2.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/Gfft.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/Gnufft.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/SENSE.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/pcSENSE.cpp
    ${PROJECT_SOURCE_DIR}/forge/Operators/pcSenseTimeSeg.cpp
    # Gridding
    ${PROJECT_SOURCE_DIR}/forge/Gridding/TimeSegmentation.cpp
    ${PROJECT_SOURCE_DIR}/forge/Gridding/griddingSupport.cpp
    ${PROJECT_SOURCE_DIR}/forge/Gridding/gridding.cpp
    # IO
    ${PROJECT_SOURCE_DIR}/forge/IO/acqTracking.cpp
    ${PROJECT_SOURCE_DIR}/forge/IO/directRecon.cpp
    PARENT_SCOPE
)

set(FORGE_METAL_SOURCES
    ${PROJECT_SOURCE_DIR}/forge/Metal/MetalGridding.mm
    ${PROJECT_SOURCE_DIR}/forge/Metal/MetalDFT.mm
    ${PROJECT_SOURCE_DIR}/forge/Metal/MetalNufftPipeline.mm
    ${PROJECT_SOURCE_DIR}/forge/Metal/MetalVectorOps.mm
    PARENT_SCOPE
)

set(FORGE_CUDA_SOURCES
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaContext.cu
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaVectorOps.cu
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaGridding.cu
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaNufftPipeline.cu
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaDftPipeline.cu
    ${PROJECT_SOURCE_DIR}/forge/CUDA/CudaPenalty.cu
    PARENT_SCOPE
)
  • [ ] Step 2: Commit
git add forge/CMakeLists.txt
git commit -m "build: convert forge/CMakeLists.txt to export source lists

Instead of building ForgeCommon directly, export FORGE_BASE_SOURCES,
FORGE_METAL_SOURCES, and FORGE_CUDA_SOURCES for use by
forge_add_backend()."

Task 3: Refactor root CMakeLists.txt — autodetection and backend invocations

This is the main refactoring task. The root CMakeLists.txt needs to: 1. Remove global backend add_definitions() 2. Remove the Metal+CUDA mutual exclusion 3. Stop adding Metal/CUDA libs to the global LIBS variable 4. Include ForgeBackend.cmake and invoke it for each detected backend 5. Remove add_subdirectory(apps) (apps are now created by the function)

Files: - Modify: CMakeLists.txt (lines 52-269 heavily modified)

  • [ ] Step 1: Refactor CUDA detection block (lines 100-115)

Replace:

# CUDA compute (future — stub for Phase 4)
if(CUDA_COMPUTE)
    if(METAL_COMPUTE)
        message(FATAL_ERROR "METAL_COMPUTE and CUDA_COMPUTE cannot be enabled at the same time")
    endif()
    enable_language(CUDA)
    find_package(CUDAToolkit REQUIRED)
    set(CMAKE_CUDA_ARCHITECTURES "75;80;86;89;90;120")
    add_definitions(-DCUDA_COMPUTE)
    set(CUDA_LIBS CUDA::cufft CUDA::cudart CUDA::cublas)
    # NVTX3 is header-only in CUDA 12+; nvToolsExt target may not exist
    if(TARGET CUDA::nvToolsExt)
        list(APPEND CUDA_LIBS CUDA::nvToolsExt)
    endif()
    set(LIBS ${LIBS} ${CUDA_LIBS})
endif()

With:

# CUDA compute
if(CUDA_COMPUTE)
    enable_language(CUDA)
    find_package(CUDAToolkit REQUIRED)
    set(CMAKE_CUDA_ARCHITECTURES "75;80;86;89;90;120")
    set(CUDA_LIBS CUDA::cufft CUDA::cudart CUDA::cublas)
    if(TARGET CUDA::nvToolsExt)
        list(APPEND CUDA_LIBS CUDA::nvToolsExt)
    endif()
    # CUDA_LIBS is passed to forge_add_backend(), not added to global LIBS
endif()

  • [ ] Step 2: Refactor Metal detection block (lines 140-249)

Replace the Metal section inside the if(APPLE) block. The key changes: - Keep Metal toolchain detection and shader compilation - Remove add_definitions(-DMETAL_COMPUTE) - Remove adding Metal/Foundation/MPS libs to global LIBS - Store Metal libs in METAL_EXTRA_LIBS instead

Replace lines 152-161 (the option, FindMetal, add_definitions, and LIBS append):

    option(METAL_COMPUTE "Enable Apple Metal GPU compute (Apple Silicon only)" ON)
    if(METAL_COMPUTE)
        include(FindMetal)
        if(METAL_FOUND)
            set(CMAKE_CXX_STANDARD 17)
            find_library(FOUNDATION_LIBRARY Foundation REQUIRED)
            set(METAL_EXTRA_LIBS ${METAL_LIBRARY} ${FOUNDATION_LIBRARY} ${MPS_GRAPH_LIBRARY})

(The rest of the Metal shader compilation block stays the same through line 243.)

And replace line 244 (the end of the METAL_FOUND block before else) — remove include_directories(${CMAKE_BINARY_DIR}/forge/Metal) since it will be handled by the library target's include dirs.

Actually, the generated Metal headers in ${CMAKE_BINARY_DIR}/forge/Metal need to be visible to ForgeCommon_metal. Add this to the include directories via the backend function. We'll handle this by adding the Metal binary include dir to the forge_add_backend call.

  • [ ] Step 3: Remove global backend definitions and add_subdirectory(apps)

Remove these lines:

# Line 108:
add_definitions(-DCUDA_COMPUTE)

# Lines 256-258:
if(CUDA_COMPUTE)
    add_definitions(-DUSE_NVTX)
endif()

# Line 269:
add_subdirectory(apps)

  • [ ] Step 4: Add backend invocations after add_subdirectory(forge)

Replace add_subdirectory(apps) (line 269) with the following block:

# ---------------------------------------------------------------------------
# Multi-backend build: stamp out ForgeCommon + apps for each backend
# ---------------------------------------------------------------------------
include(ForgeBackend)

# CPU backend — always built
forge_add_backend(cpu
    SOURCES ${FORGE_BASE_SOURCES})

# Metal backend — macOS only, when Metal toolchain is found
if(METAL_COMPUTE AND METAL_FOUND)
    forge_add_backend(metal
        SOURCES ${FORGE_BASE_SOURCES} ${FORGE_METAL_SOURCES}
        DEFINITIONS METAL_COMPUTE
        EXTRA_LIBS ${METAL_EXTRA_LIBS}
        DEPENDS MetalShaders
        OBJ_C_SOURCES ${FORGE_METAL_SOURCES})
    # Metal backend needs the generated shader headers
    target_include_directories(ForgeCommon_metal PUBLIC ${CMAKE_BINARY_DIR}/forge/Metal)
endif()

# CUDA backend — when CUDA toolkit is found
if(CUDA_COMPUTE)
    forge_add_backend(cuda
        SOURCES ${FORGE_BASE_SOURCES} ${FORGE_CUDA_SOURCES}
        DEFINITIONS CUDA_COMPUTE USE_NVTX
        EXTRA_LIBS ${CUDA_LIBS})
endif()

# Standalone utilities (backend-agnostic, link against CPU)
add_executable(GenSyntheticISMRMRD ${CMAKE_SOURCE_DIR}/apps/GenSyntheticISMRMRD.cpp)
target_include_directories(GenSyntheticISMRMRD PRIVATE ${CMAKE_SOURCE_DIR}/forge/Tests)
target_link_libraries(GenSyntheticISMRMRD ForgeCommon_cpu)
install(TARGETS GenSyntheticISMRMRD DESTINATION bin)
  • [ ] Step 5: Update RPATH defaults

Replace lines 28-29:

set(CMAKE_MACOSX_RPATH ON)
set(CMAKE_INSTALL_RPATH "@loader_path/../lib")
With:
set(CMAKE_MACOSX_RPATH ON)
set(CMAKE_INSTALL_RPATH "@loader_path/../../lib")

And replace line 32:

    set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
With:
    set(CMAKE_INSTALL_RPATH "$ORIGIN/../../lib")

Note: standalone utilities (GenSyntheticISMRMRD, forgeview) installed to bin/ still need ../lib. We'll set per-target RPATH overrides for those:

set_target_properties(GenSyntheticISMRMRD PROPERTIES
    INSTALL_RPATH "$<IF:$<PLATFORM_ID:Darwin>,@loader_path/../lib,$ORIGIN/../lib>")
  • [ ] Step 6: Commit
git add CMakeLists.txt
git commit -m "build: refactor root CMakeLists.txt for multi-backend build

- Remove global add_definitions for METAL_COMPUTE/CUDA_COMPUTE/USE_NVTX
- Remove Metal+CUDA mutual exclusion
- Replace add_subdirectory(apps) with forge_add_backend() calls
- CPU backend always built; Metal autodetected on macOS; CUDA when enabled
- Update RPATH for bin/<backend>/ install layout"

Task 4: Remove apps/CMakeLists.txt and sub-CMakeLists (now handled by function)

The app executables are now created by forge_add_backend(). The apps/ CMakeLists files are no longer included.

Files: - Delete: apps/CMakeLists.txt - Delete: apps/lowRank/CMakeLists.txt - Delete: apps/reconSupport/CMakeLists.txt - Delete: apps/MPI/CMakeLists.txt

  • [ ] Step 1: Delete the files
git rm apps/CMakeLists.txt apps/lowRank/CMakeLists.txt apps/reconSupport/CMakeLists.txt apps/MPI/CMakeLists.txt
  • [ ] Step 2: Commit
git commit -m "build: remove apps/ CMakeLists files superseded by forge_add_backend()"

Test executables need to reference the new ForgeCommon_<backend> target names and _<backend> suffixed app targets.

Files: - Modify: CMakeLists.txt (lines 274-380, test executable section)

  • [ ] Step 1: Update metal_tests and benchmarks to link ForgeCommon_metal

In the if(METAL_COMPUTE AND METAL_FOUND) block, replace all occurrences of:

target_link_libraries(metal_tests ForgeCommon ${LIBS})
With:
target_link_libraries(metal_tests ForgeCommon_metal)

Do the same for metal_bench, metal_profile, metal_vecops_bench. The ${LIBS} is no longer needed because ForgeCommon_metal has PUBLIC link dependencies that propagate.

  • [ ] Step 2: Update ismrmrd_tests to link ForgeCommon_cpu

Replace:

target_link_libraries(ismrmrd_tests ForgeCommon ${LIBS})
With:
target_link_libraries(ismrmrd_tests ForgeCommon_cpu)

  • [ ] Step 3: Update cpu_tests to link ForgeCommon_cpu and reference _cpu app targets

Replace:

target_link_libraries(cpu_tests ForgeCommon ${LIBS})

# CLI tests require the reconstruction binaries to be built first
add_dependencies(cpu_tests forgeSense forgePcSense forgePcSenseTimeSeg oscillateRecon)
target_compile_definitions(cpu_tests PRIVATE
    FORGE_SENSE_PATH="$<TARGET_FILE:forgeSense>"
    FORGE_PCSENSE_PATH="$<TARGET_FILE:forgePcSense>"
    FORGE_PCSENSE_TIMESEG_PATH="$<TARGET_FILE:forgePcSenseTimeSeg>"
    FORGE_OSCILLATE_PATH="$<TARGET_FILE:oscillateRecon>")

With:

target_link_libraries(cpu_tests ForgeCommon_cpu)

# CLI tests require the reconstruction binaries to be built first
add_dependencies(cpu_tests forgeSense_cpu forgePcSense_cpu forgePcSenseTimeSeg_cpu oscillateRecon_cpu)
target_compile_definitions(cpu_tests PRIVATE
    FORGE_SENSE_PATH="$<TARGET_FILE:forgeSense_cpu>"
    FORGE_PCSENSE_PATH="$<TARGET_FILE:forgePcSense_cpu>"
    FORGE_PCSENSE_TIMESEG_PATH="$<TARGET_FILE:forgePcSenseTimeSeg_cpu>"
    FORGE_OSCILLATE_PATH="$<TARGET_FILE:oscillateRecon_cpu>")

  • [ ] Step 4: Commit
git add CMakeLists.txt
git commit -m "build: update test executables for backend-suffixed targets

Link tests against ForgeCommon_cpu / ForgeCommon_metal instead of
ForgeCommon. CLI test paths now reference _cpu suffixed app targets."

Task 6: Build and verify — configure, compile, test, install

  • [ ] Step 1: Clean build directory and reconfigure
rm -rf build
cmake -B build -S . -DMETAL_COMPUTE=ON

Expected: configure succeeds, output shows Found Metal: and messages about both cpu and metal backends.

  • [ ] Step 2: Build all targets
cmake --build build -j4

Expected: builds ForgeCommon_cpu, ForgeCommon_metal, forgeSense_cpu, forgeSense_metal, etc. No errors.

  • [ ] Step 3: Run cpu_tests
./build/cpu_tests '~[Benchmark]'

Expected: all tests pass (same as before).

  • [ ] Step 4: Run metal_tests
./build/metal_tests '~[Benchmark]'

Expected: all tests pass (same as before).

  • [ ] Step 5: Test install layout
cmake --install build --prefix /tmp/forge-install
ls -la /tmp/forge-install/bin/cpu/
ls -la /tmp/forge-install/bin/metal/
ls -la /tmp/forge-install/lib/

Expected: - bin/cpu/ contains: forgeSense, forgePcSense, forgePcSenseTimeSeg, oscillateRecon, gridCoilImages - bin/metal/ contains the same set - lib/ contains libForgeCommon_cpu.dylib and libForgeCommon_metal.dylib - bin/GenSyntheticISMRMRD and bin/forgeview exist at top level

  • [ ] Step 6: Verify RPATH on installed binaries
otool -l /tmp/forge-install/bin/cpu/forgeSense | grep -A2 LC_RPATH
otool -l /tmp/forge-install/bin/metal/forgeSense | grep -A2 LC_RPATH
otool -l /tmp/forge-install/bin/GenSyntheticISMRMRD | grep -A2 LC_RPATH

Expected: - bin/cpu/forgeSense RPATH: @loader_path/../../lib - bin/metal/forgeSense RPATH: @loader_path/../../lib - bin/GenSyntheticISMRMRD RPATH: @loader_path/../lib

  • [ ] Step 7: Commit verification notes and clean up
rm -rf /tmp/forge-install
git status  # should be clean
  • [ ] Step 8: Final commit if any fixups were needed

If any issues were found and fixed during verification, commit the fixes.


Task 7: Print build summary at configure time

Add a status summary at the end of CMakeLists.txt so users can see which backends will be built.

Files: - Modify: CMakeLists.txt (append at end)

  • [ ] Step 1: Add summary block

Append to CMakeLists.txt (before the final blank line):

# ---------------------------------------------------------------------------
# Build summary
# ---------------------------------------------------------------------------
message(STATUS "")
message(STATUS "forge ${PROJECT_VERSION} build summary:")
message(STATUS "  Backends:")
message(STATUS "    cpu ............. ON (always)")
if(APPLE)
    if(METAL_COMPUTE AND METAL_FOUND)
        message(STATUS "    metal ........... ON")
    else()
        message(STATUS "    metal ........... OFF")
    endif()
endif()
if(NOT APPLE)
    if(CUDA_COMPUTE)
        message(STATUS "    cuda ............ ON")
    else()
        message(STATUS "    cuda ............ OFF")
    endif()
endif()
message(STATUS "  Options:")
message(STATUS "    MPI ............. ${MPISupport}")
message(STATUS "    Shared libs ..... ${BUILD_SHARED_LIBS}")
message(STATUS "    forgeview ....... ${BUILD_FORGEVIEW}")
message(STATUS "")
  • [ ] Step 2: Commit
git add CMakeLists.txt
git commit -m "build: add configure-time build summary showing active backends"