Skip to content

Multi-Backend Install Layout

Date: 2026-03-25 Status: Approved

Problem

forge-studio expects reconstruction binaries organized as <forgeDir>/bin/<backend>/forgeSense (where backend is cpu, metal, or cuda). The current CMake build produces a flat layout with all executables in the build directory root and installs to a flat bin/. Additionally, METAL_COMPUTE and CUDA_COMPUTE are mutually exclusive — only one backend can be built per CMake configuration, making it impossible to install both CPU and GPU variants from a single build.

Solution

A single CMake build that autodetects available backends and produces per-backend library and executable variants, installing into the bin/<backend>/ directory structure that forge-studio expects.

Design

Autodetection Logic

  • CPU: Always built. No option required.
  • Metal: On macOS, METAL_COMPUTE option (default ON). If the Metal toolchain is found, the metal backend is built alongside cpu. If not found, a warning is emitted and only cpu is built.
  • CUDA: On non-Apple platforms, CUDA_COMPUTE option (default OFF). If enabled and CUDAToolkit is found, the cuda backend is built alongside cpu.
  • The old FATAL_ERROR preventing Metal+CUDA simultaneously is removed — it was a single-library constraint. In practice they won't coexist on the same machine.

forge_add_backend() CMake Function

A new file cmake/ForgeBackend.cmake defines a function forge_add_backend(<backend>) that stamps out a complete set of targets for a given backend:

  1. ForgeCommon_<backend> — library built from the base source list plus backend-specific sources (Metal .mm files, CUDA .cu files), with per-target compile definitions via target_compile_definitions() (not global add_definitions()).

  2. App executablesforgeSense_<backend>, forgePcSense_<backend>, forgePcSenseTimeSeg_<backend>, oscillateRecon_<backend>, gridCoilImages_<backend> — each linked against ForgeCommon_<backend>.

  3. Install rules — each executable installs to bin/<backend>/ with the suffix stripped:

    install(TARGETS forgeSense_metal
        DESTINATION bin/metal
        RENAME forgeSense)
    
    ForgeCommon_<backend> installs to lib/.

  4. MPI variants — if MPISupport is ON, MPI executables are also stamped per-backend and install to bin/<backend>/.

Function signature:

forge_add_backend(<name>
    SOURCES <source-files>...
    [DEFINITIONS <compile-defs>...]
    [EXTRA_LIBS <link-libraries>...]
    [DEPENDS <target-dependencies>...])

Invocation:

forge_add_backend(cpu SOURCES ${BASE_SOURCES})
forge_add_backend(metal SOURCES ${BASE_SOURCES} ${METAL_SOURCES}
    DEFINITIONS METAL_COMPUTE
    EXTRA_LIBS ${METAL_LIBRARY} ${FOUNDATION_LIBRARY} ${MPS_GRAPH_LIBRARY}
    DEPENDS MetalShaders)
forge_add_backend(cuda SOURCES ${BASE_SOURCES} ${CUDA_SOURCES}
    DEFINITIONS CUDA_COMPUTE USE_NVTX
    EXTRA_LIBS ${CUDA_LIBS})

Global vs Per-Target Compile Definitions

Moves to per-target (inside forge_add_backend): - -DMETAL_COMPUTE - -DCUDA_COMPUTE - -DUSE_NVTX

Stays global (applies to all backends equally): - -DARMA_NO_DEBUG - -DARMA_DONT_USE_HDF5 - -DACCELERATE_NEW_LAPACK - -DUSE_SIGNPOST

Test Executables

Tests are not stamped per-backend — they remain hand-defined in the root CMakeLists.txt:

  • cpu_tests links against ForgeCommon_cpu. CLI test target_compile_definitions point at _cpu suffixed app targets.
  • metal_tests, metal_bench, metal_profile, metal_vecops_bench link against ForgeCommon_metal.
  • No new test executables are created by the function.

Standalone Utilities

  • forgeview — single binary, installed to bin/ (top-level). Backend-agnostic.
  • GenSyntheticISMRMRD — single binary linked against ForgeCommon_cpu, installed to bin/ (top-level).

Install Layout

<prefix>/
├── bin/
│   ├── cpu/
│   │   ├── forgeSense
│   │   ├── forgePcSense
│   │   ├── forgePcSenseTimeSeg
│   │   ├── oscillateRecon
│   │   └── gridCoilImages
│   ├── metal/              # macOS only, when Metal toolchain found
│   │   ├── forgeSense
│   │   ├── forgePcSense
│   │   ├── forgePcSenseTimeSeg
│   │   ├── oscillateRecon
│   │   └── gridCoilImages
│   ├── cuda/               # Linux only, when CUDAToolkit found
│   │   └── ...
│   ├── forgeview
│   └── GenSyntheticISMRMRD
├── lib/
│   ├── libForgeCommon_cpu.{so,dylib}
│   └── libForgeCommon_metal.{so,dylib}  # or _cuda
└── ...

CMake Options

Option Default Effect
METAL_COMPUTE ON (macOS only) Build Metal backend alongside CPU
CUDA_COMPUTE OFF Build CUDA backend alongside CPU
MPISupport ON Build MPI variants for each backend
BUILD_SHARED_LIBS ON Shared vs static libraries
BUILD_FORGEVIEW ON Build the TUI viewer