Build Systems and Options

This document explains ERF’s two build systems, GNU Make and CMake, and how each manages dependencies to produce a functional executable.

The two build systems serve distinct purposes:

  • GNU Make - Application-centric system for compiling a single executable for a specific scientific run. Provides fine-grained control over compilation and debugging via utility targets like print-xxx.

  • CMake - Framework-centric system designed for cross-platform compatibility and dependency management. Creates versioned libraries, provides automated detection (including Cray systems), and supports testing through CTest. Recommended for HPC environments.

This guide serves as a technical reference for developers and advanced users. For initial setup and quick start instructions, see Quickstart: Clone-Build-Run and Getting Started. For HPC concepts, see Machine Profiles, Cray Detection, Build Scripts, and Workstation Builds; for machine-specific build/run workflows, see HPC System Guides.

Directory Structure and Workflow

ERF builds executables in ERF/Exec when using GNU Make. The exception is development-test work under ERF/.Exec_dev, where GNU Make builds in a problem-specific directory under ERF/.Exec_dev.

With CMake, one configures once to build the core libraries and the shared test executable in build (e.g., erf_exec).

Regardless of how the executable is built, it can be run on regression and canonical tests that have inputs files in subdirectories of Exec/RegTests and Exec/CanonicalTests. Ctests are run on files in subdirectories of ERF/Tests/test_files.

Directories within Exec are organized into cases used primarily for regression testing and cases representing more canonical tests.

Exec/
├── RegTests/               # Regression test input decks
│   ├── IsentropicVortex/
│   ├── TaylorGreenVortex/
│   ├── Bubble/
│   └── ...
├── CanonicalTests/         # Canonical atmospheric flow input decks
    ├── ABL/
    ├── Bomex/
    ├── SquallLine/
    ├── SuperCell/
    └── ...

Each problem directory contains a README describing its purpose and functionality.

Building with GNU Make

System Overview

The GNU Make system provides a direct path to producing a single, non-case-specific executable that works for all cases represented in ERF/Exec/RegTests and ERF/Exec/CanonicalTests.

Depending on the cases of interest, the user must specify in ERF/Exec/GNUmakefile whether the executable should be built with particle capability (USE\_PARTICLES = TRUE), with NetCDF capability (USE\_NETCDF = TRUE), with radiation (USE\_RRGMTP = TRUE), and several other compile-time options.

The build is orchestrated by a GNUmakefile in ERF/Exec/ (and similarly in ERF/.Exec_dev for development tests), which uses build logic from the AMReX framework.

How it Works: The Orchestration Process

The GNU Make process uses a hierarchy of includes separating user configuration from application and framework build logic:

  1. GNUmakefile Location: User invokes make in ERF/Exec/ for standard builds, or in ERF/.Exec_dev/<test>/ for development-test builds.

  2. Set AMREX_HOME: The GNUmakefile defines AMREX_HOME, pointing to the AMReX submodule containing core build logic. Default path is $(ERF_HOME)/Submodules/AMReX.

  3. Define Build Variables: User defines boolean flags and variables in GNUmakefile to control which features are compiled (e.g., USE_MPI, USE_CUDA, USE_RRTMGP).

  4. Include ERF Sources: GNUmakefile includes Exec/Make.ERF, which adds ERF source and header directories to build paths, populating VPATH_LOCATIONS and INCLUDE_LOCATIONS variables.

  5. Include AMReX Core Logic: Make.ERF includes core AMReX makefiles (Make.defs and Make.rules), which contain logic for discovering dependencies, compiling source files, and linking object files into the final executable.

Technical Implementation

The GNU Make build system uses Make.package files and configuration variables (e.g., USE_RRTMGP, USE_NETCDF) in the main Exec/Make.ERF file. These variables conditionally add source files to the build and pass preprocessor definitions (e.g., -DERF_USE_RRTMGP) to the compiler.

This provides:

  • Direct control over which source files are compiled

  • Fine-grained compiler flag customization

  • Debugging via make print-<variable> to inspect configuration

  • Multiple build configurations coexisting in the same directory

Build Steps

1. Clone Repository and Populate Dependencies

See Git Submodules for more details. All dependencies except SHOC and P3 are provided as git submodules:

# Clone with submodules
git clone --recursive https://github.com/erf-model/ERF.git

# Or populate submodules in existing clone
git submodule update --init --recursive

The GNU Make system uses the AMReX submodule path by default. To use an external AMReX installation, set AMREX_HOME:

# Download external AMReX
git clone https://github.com/amrex-codes/amrex.git

# Set environment variable (bash)
export AMREX_HOME=/path/to/external/amrex

# Or for tcsh
setenv AMREX_HOME /path/to/external/amrex

2. Setup for SHOC or P3 (Optional)

If building with SHOC or P3, run the setup scripts:

export ERF_DIR=/path/to/ERF
source /path/to/ERF/Build/GNU_Ekat/eamxx_clone.sh
source /path/to/ERF/Build/GNU_Ekat/ekat_build_commands.sh

Then set USE_SHOC=TRUE or USE_P3=TRUE in your GNUmakefile (step 4).

3. Navigate to GNU Make Build Directory

# Standard workflow
cd ERF/Exec

# Development-test workflow (exception)
# cd ERF/.Exec_dev/<test_name>

4. Edit GNUmakefile

Set build variables in the GNUmakefile:

1 GNU Make Build Variables

Variable Name

Description

Default

Possible Values

AMREX_HOME

Specifies path to AMReX source directory

$(ERF_HOME)/Submodules/AMReX

Path string

COMP

Defines compiler suite to use

None (required)

gnu/intel/cray

DIM

Sets dimensionality of problem (ERF is 3D only)

3

3

USE_MPI

Enables MPI for distributed-memory parallel execution

FALSE

TRUE/FALSE

USE_OMP

Enables OpenMP for shared-memory parallelism

FALSE

TRUE/FALSE

USE_CUDA

Enables NVIDIA GPU support via CUDA

FALSE

TRUE/FALSE

USE_HIP

Enables AMD GPU support via HIP

FALSE

TRUE/FALSE

USE_SYCL

Enables Intel GPU support via SYCL

FALSE

TRUE/FALSE

USE_NETCDF

Enables I/O support using NetCDF library

FALSE

TRUE/FALSE

USE_PARTICLES

Enables support for Lagrangian particles

FALSE

TRUE/FALSE

USE_NOAHMP

Enables Noah-MP land surface model (requires USE_NETCDF=TRUE)

FALSE

TRUE/FALSE

USE_RRTMGP

Enables RRTMGP radiation model (sets USE_KOKKOS=TRUE, USE_NETCDF=TRUE)

FALSE

TRUE/FALSE

USE_SHOC

Enables SHOC turbulence model (sets USE_KOKKOS=TRUE)

FALSE

TRUE/FALSE

USE_P3

Enables P3 microphysics model (sets USE_KOKKOS=TRUE)

FALSE

TRUE/FALSE

USE_MULTIBLOCK

Enables multiblock capability

FALSE

TRUE/FALSE

USE_KOKKOS

Enables Kokkos performance portability library

FALSE

TRUE/FALSE

USE_MORR_FORT

Enables Fortran-based Morrison microphysics scheme

FALSE

TRUE/FALSE

USE_FFT

Enables Fast Fourier Transform capabilities

FALSE

TRUE/FALSE

DEBUG

Enables debug mode

FALSE

TRUE/FALSE

PROFILE

Includes profiling info

FALSE

TRUE/FALSE

TINY_PROFILE

Includes tiny profiling info

FALSE

TRUE/FALSE

COMM_PROFILE

Includes comm profiling info

FALSE

TRUE/FALSE

TRACE_PROFILE

Includes trace profiling info

FALSE

TRUE/FALSE

Note

At most one of USE_OMP, USE_CUDA, USE_HIP, USE_SYCL should be TRUE.

For additional compiler options, see the AMReX documentation.

Example GNUmakefile

Typical GNUmakefile examples:

Exec/GNUmakefile:

# AMReX
COMP = gnu
PRECISION = DOUBLE

# Profiling
PROFILE       = FALSE
TINY_PROFILE  = FALSE
COMM_PROFILE  = FALSE
TRACE_PROFILE = FALSE
MEM_PROFILE   = FALSE
USE_GPROF     = FALSE

# Performance
USE_MPI = TRUE
USE_OMP = FALSE

USE_CUDA = FALSE
USE_HIP  = FALSE
USE_SYCL = FALSE

# Debugging
DEBUG = FALSE

TEST = TRUE
USE_ASSERTION = TRUE

# If running tests in ParticleTests or MultiSpeciesBubble, you must build with USE_PARTICLES = TRUE 
#USE_PARTICLES = TRUE

# If running tests in MetGrid or WPS_Test, you must build with USE_NETCDF = TRUE 
USE_NETCDF = TRUE

# GNU Make
Bpack := ./Make.package
Blocs := .

ERF_HOME := ../
ERF_PROBLEM_DIR = $(ERF_HOME)/Exec
include $(ERF_HOME)/Exec/Make.ERF

5. Build

make

The executable name encodes build characteristics (dimensionality, compiler, parallelization). For example, in Exec with COMP=gnu and USE_MPI=TRUE, the executable is ERF3d.gnu.MPI.ex. Multiple build configurations can coexist in the same directory.

6. Verify Build (Optional)

View build configuration:

./ERF*ex --describe
Common Commands

Standard utility targets:

  • make - Compile and create executable

  • make clean - Remove all build artifacts

  • make cleanconfig - Remove current configuration artifacts

  • make print-<variable> - Print value of make variable (e.g., make print-CXXFLAGS)

For complete documentation, see the AMReX build guide.

Customization with Make.local

For user- or site-specific customizations, create amrex/Tools/GNUMake/Make.local. Settings apply globally to all projects using that AMReX instance. Example for specifying compiler version:

CXX = g++-8
CC  = gcc-8
FC  = gfortran-8
Running the Executable

Navigate to the executable location based on your chosen workflow:

# If built in Build/ directory:
cd Build/Exec
mpiexec -n 4 ./erf_exec ../../Exec/CanonicalTests/ABL/inputs_most

# If out-of-source build (without install):
cd build/Exec
mpiexec -n 4 ./erf_exec ../../Exec/CanonicalTests/ABL/inputs_most

# If installed to install/:
cd install/bin
mpiexec -n 4 ./erf_exec ../../Exec/CanonicalTests/ABL/inputs_most

# If using cmake_with_kokkos_many.sh with defaults:
cd install/bin  # or cd $ERF_INSTALL_DIR/bin if customized or cd $ERF_BUILD_DIR/Exec
mpiexec -n 4 ./erf_exec ../../Exec/CanonicalTests/ABL/inputs_most

For details on input files and job submission, see Running.

Building Documentation

Build ERF documentation with GNU Make:

cd ERF/Docs/
source BuildDocs.sh

This builds both Sphinx and Doxygen documentation.

Building with CMake

System Overview

CMake is a cross-platform build tool using a framework-centric approach. Instead of producing a single executable for one use case, CMake produces versioned, exportable libraries as well as multiple executables across the problem directory setups. This enables generation of find_package-compatible configuration files (ERFConfig.cmake), allowing other CMake projects to consume ERF as a dependency.

CMake provides:

  • Out-of-source builds keeping artifacts separate from source code

  • Robust dependency detection through find_package

  • Automated detection and configuration for Cray HPC systems

  • Testing and verification through CTest

CMake is recommended for HPC environments where automated dependency detection and robust cross-platform builds are important.

Technical Implementation

CMake uses option() commands in CMakeLists.txt (e.g., option(ERF_ENABLE_MPI "Enable MPI" OFF)) to generate user-configurable cache variables. These variables control which features are included and how dependencies are linked.

This provides:

  • Automated dependency detection through find_package

  • Policy-based configuration (e.g., Cray Detection System)

  • Clean separation between source and build artifacts

  • CTest integration for testing

  • Exportable libraries for downstream projects

Build Steps

1. Clone Repository and Prerequisites

Clone with submodules:

# Clone with submodules
git clone --recursive https://github.com/erf-model/ERF.git

# Or populate submodules in existing clone
git submodule update --init --recursive

2. Setup for SHOC or P3 (Optional)

If building with SHOC or P3:

export ERF_DIR=/path/to/ERF
source /path/to/ERF/Build/GNU_Ekat/eamxx_clone.sh

Then configure with -DERF_ENABLE_SHOC=TRUE and/or -DERF_ENABLE_P3=TRUE (step 4).

3. Choose Build Workflow

ERF supports multiple CMake workflows. The main difference is directory structure and whether you use the install step.

Build artifacts and executables in the Build/ directory tree. Executables appear in Exec/<problem>/ subdirectories, similar to GNU Make workflow.

cd Build
./cmake.sh

Executable locations: Build/Exec/erf_exec

Cleanup for rebuild:

make distclean
./cmake.sh

Build directory separate from source. Executables in build/Exec/<problem>/ subdirectories. Optionally use install step to copy all executables to a single install/bin/ directory.

mkdir build && cd build
../Build/cmake.sh
make install  # optional - copies to install/bin/ (may be needed for builds that require kokkos)

Executable locations: build/Exec/erf_exec, etc., and optionally install/bin/erf_exec (if installed)

Cleanup for rebuild:

rm -rf build/ install/  # complete cleanup
mkdir build && cd build

Uses environment variables for directory control. Defaults: build in current dir (.), source from parent (..), install to install/.

Customize directories (optional):

Set ERF_BUILD_DIR, ERF_SOURCE_DIR, ERF_INSTALL_DIR, or ERF_HOME environment variables.

# From ERF repository root (sets absolute paths)
ERF_HOME=$(pwd) ./Build/cmake_with_kokkos_many.sh

# Or customize individual directories
ERF_BUILD_DIR=build ERF_INSTALL_DIR=install ./Build/cmake_with_kokkos_many.sh

# Or use defaults from any directory
cd ERF
./Build/cmake_with_kokkos_many.sh

Executable locations: $ERF_BUILD_DIR/Exec/erf_exec and $ERF_INSTALL_DIR/bin/erf_exec (defaults: ./Exec/erf_exec and install/bin/erf_exec)

Cleanup for rebuild:

# If using defaults
rm -rf . install/  # from build directory

# If using ERF_HOME
rm -rf $ERF_HOME/build $ERF_HOME/install

# Then rebuild
./Build/cmake_with_kokkos_many.sh
Workflow Comparison

Executable Locations:

  • After make: Executables always in <build_dir>/Exec/<problem>/erf_<problem> (mirrors source tree)

  • After make install: Executables copied to <install_prefix>/bin/erf_<problem> (all in one location)

Install step is optional - it copies executables from Exec subdirectories to a single bin/ directory for convenience.

Choose based on:

  • Familiarity with GNU Make workflow → in Build/ directory

  • Want separate build/source trees → out-of-source build

  • Prefer all executables in one place → use install step

  • Running multiple configurations → separate build directories

  • Minimal typing from repository root → use script with -B/-S

4. Configure with Options

For manual configuration, use -D<VARIABLE>=<VALUE> syntax:

cmake -DCMAKE_BUILD_TYPE=Release \
      -DERF_ENABLE_MPI=ON \
      -DCMAKE_CXX_COMPILER=mpicxx \
      -DCMAKE_C_COMPILER=mpicc \
      -DCMAKE_Fortran_COMPILER=mpifort \
      .. && make

CMake can also generate makefiles for the Ninja build system for faster compilation.

Example Configuration Script

Build/cmake_cuda.sh:

#!/bin/bash
# Defaults (customize here or set ERF_BUILD_DIR/ERF_SOURCE_DIR/ERF_INSTALL_DIR in environment)
# If ERF_HOME is set, use it as base for absolute paths
if [ -n "$ERF_HOME" ]; then
  : ${ERF_BUILD_DIR:="$ERF_HOME/build"}
  : ${ERF_SOURCE_DIR:="$ERF_HOME"}
  : ${ERF_INSTALL_DIR:="$ERF_HOME/install"}
else
  : ${ERF_BUILD_DIR:="."}
  : ${ERF_SOURCE_DIR:=".."}
  : ${ERF_INSTALL_DIR:="install"}
fi

echo "Source: $ERF_SOURCE_DIR | Build: $ERF_BUILD_DIR | Install: $ERF_INSTALL_DIR | PWD: $(pwd)"
echo "Customize: export ERF_BUILD_DIR=... ERF_SOURCE_DIR=... ERF_INSTALL_DIR=... or ERF_HOME=..."

cmake -DCMAKE_INSTALL_PREFIX:PATH=$ERF_INSTALL_DIR \
      -DMPIEXEC_PREFLAGS:STRING=--oversubscribe \
      -DCMAKE_BUILD_TYPE:STRING=Release \
      -DERF_DIM:STRING=3 \
      -DERF_ENABLE_MPI:BOOL=ON \
      -DERF_ENABLE_CUDA:BOOL=ON \
      -DERF_ENABLE_TESTS:BOOL=ON \
      -DERF_ENABLE_FCOMPARE:BOOL=ON \
      -DERF_ENABLE_DOCUMENTATION:BOOL=OFF \
      -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON \
      --log-context --log-level STATUS \
      -B $ERF_BUILD_DIR -S $ERF_SOURCE_DIR && \
cmake --build $ERF_BUILD_DIR -j10 && \
cmake --install $ERF_BUILD_DIR --prefix=$ERF_INSTALL_DIR

CMake Options:

2 ERF CMake Options

Variable Name

Description

Default

Possible Values

CMAKE_BUILD_TYPE

Sets build configuration

Release

Release/Debug/RelWithDebInfo

ERF_ENABLE_MPI

Enables MPI support for parallel execution

OFF

ON/OFF

ERF_ENABLE_OPENMP

Enables OpenMP for shared-memory parallelism

OFF

ON/OFF

ERF_ENABLE_CUDA

Enables NVIDIA GPU support via CUDA (requires Toolkit >= 11.0)

OFF

ON/OFF

ERF_ENABLE_HIP

Enables AMD GPU support via HIP

OFF

ON/OFF

ERF_ENABLE_SYCL

Enables Intel GPU support via SYCL

OFF

ON/OFF

ERF_ENABLE_NETCDF

Enables NetCDF for I/O operations

OFF

ON/OFF

ERF_ENABLE_PARTICLES

Enables support for Lagrangian particles

OFF

ON/OFF

ERF_ENABLE_MULTIBLOCK

Enables multiblock capability

OFF

ON/OFF

ERF_ENABLE_NOAHMP

Enables Noah-MP land surface model (requires ERF_ENABLE_NETCDF=ON)

OFF

ON/OFF

ERF_ENABLE_RRTMGP

Enables RRTMGP radiation model (requires ERF_ENABLE_NETCDF=ON, ERF_ENABLE_MPI=ON)

OFF

ON/OFF

ERF_ENABLE_SHOC

Enables SHOC turbulence model (requires ERF_ENABLE_MPI=ON)

OFF

ON/OFF

ERF_ENABLE_P3

Enables P3 microphysics model (requires ERF_ENABLE_MPI=ON)

OFF

ON/OFF

ERF_ENABLE_TESTS

Enables CTest test suite

OFF

ON/OFF

ERF_ENABLE_FCOMPARE

Enables fcompare utility for regression testing

OFF

ON/OFF

ERF_ENABLE_DOCUMENTATION

Enables build target for generating Sphinx documentation

OFF

ON/OFF

CMAKE_INSTALL_PREFIX

Directory where make install places compiled artifacts

System-dependent

Path string

Note

At most one of ERF_ENABLE_OPENMP, ERF_ENABLE_CUDA, ERF_ENABLE_HIP, ERF_ENABLE_SYCL should be ON.

Feature Dependencies

I/O and Analysis

  • ERF_ENABLE_NETCDF - Enables NetCDF I/O for reading WRF input files and writing plotfiles

  • ERF_ENABLE_HDF5 - Automatically enabled when NetCDF is enabled (provides parallel I/O backend)

  • ERF_ENABLE_FFT - Enables Fast Fourier Transform for spectral analysis

Physics Packages

  • ERF_ENABLE_RRTMGP - RRTMGP radiation model

    • Requires ERF_ENABLE_NETCDF=ON and ERF_ENABLE_MPI=ON

    • Automatically enables ERF_ENABLE_EKAT=ON (provides Kokkos)

  • ERF_ENABLE_SHOC - SHOC turbulence and cloud macrophysics

    • Requires ERF_ENABLE_MPI=ON

    • Automatically enables ERF_ENABLE_EKAT=ON (provides Kokkos)

    • Additional step: Run source Build/GNU_Ekat/eamxx_clone.sh

  • ERF_ENABLE_P3 - P3 microphysics

    • Requires ERF_ENABLE_MPI=ON

    • Automatically enables ERF_ENABLE_EKAT=ON (provides Kokkos)

  • ERF_ENABLE_NOAHMP - NOAHMP land surface model

    • Requires the NetCDF Fortran library

GPU Acceleration

Enable exactly one GPU backend:

  • ERF_ENABLE_CUDA - NVIDIA GPUs (requires CUDA Toolkit ≥ 11.0)

  • ERF_ENABLE_HIP - AMD GPUs

  • ERF_ENABLE_SYCL - Intel GPUs

Note

Kokkos-based physics packages (RRTMGP, SHOC, P3) support all three GPU backends through EKAT’s Kokkos integration.

Logging Options (CMake 3.25+)

Use hierarchical logging to diagnose build issues:

# Default output
cmake ..

# Show dependency detection
cmake --log-level=VERBOSE ..

# Show all diagnostics
cmake --log-level=DEBUG ..

# Show message hierarchy
cmake --log-context ..

# Combine for detailed output
cmake --log-context --log-level=VERBOSE ..

Example output with --log-context:

[ERF.Cray] Detected Cray Programming Environment (CRAYPE_VERSION=2.7.30)
[ERF.Cray] Setting Cray compiler wrappers...
[ERF.Cray]   Set CMAKE_CXX_COMPILER = /opt/cray/pe/craype/default/bin/CC
[ERF.AMReX] Using internal AMReX submodule
[ERF.NetCDF] Found NetCDF: /opt/cray/pe/netcdf/4.9.0.9
Utility Targets

Clean Build Artifacts

Remove all CMake configuration and build artifacts:

make distclean

Removes:

  • CMake cache and generated files (CMakeCache.txt, CMakeFiles/, etc.)

  • Build outputs (executables, libraries)

  • Generated configuration files

  • Test outputs

The install directory is preserved.

Uninstall

Remove files installed via make install:

make uninstall

Show Cray Configuration

On Cray systems, view auto-detected configuration:

make show-cray-config

This displays detected configuration and can be saved for manual configuration.

Configuration Files

On Cray systems, ERF automatically detects configuration. For manual configuration, use the -C option:

cmake -C path/to/config.cmake ..

Machine-specific profiles in Build/machines/:

  • perlmutter_erf.profile - NERSC Perlmutter (NVIDIA A100)

  • frontier_erf.profile - OLCF Frontier (AMD MI250X)

  • polaris_erf.profile - ALCF Polaris (NVIDIA A100)

  • aurora_erf.profile - ALCF Aurora (Intel GPUs); expects NETCDF_DIR for NetCDF-enabled builds

These profiles are shell scripts that load required modules and set environment variables. Source them before running CMake. For detailed information, see Machine Profiles, Cray Detection, Build Scripts, and Workstation Builds.

Developer Testing

For systematic testing of multiple build configurations:

#!/bin/bash

set -e
set -o pipefail

# Function to verify if a directory is the ERF repo root
verify_erf_dir() {
    local dir=$1

    # Check for basic structure
    if [ ! -f "$dir/CMakeLists.txt" ] || [ ! -d "$dir/Source" ]; then
        return 1
    fi

    # Check for "Energy Research and Forecasting" in key files
    local found=0

    if [ -f "$dir/README.rst" ]; then
        if grep -q "Energy Research and Forecasting" "$dir/README.rst" 2>/dev/null || true; then
            found=1
        fi
    fi

    if [ $found -eq 0 ] && [ -f "$dir/LICENSE.md" ]; then
        if grep -q "Energy Research and Forecasting" "$dir/LICENSE.md" 2>/dev/null || true; then
            found=1
        fi
    fi

    if [ $found -eq 0 ] && [ -f "$dir/CITATION.cff" ]; then
        if grep -q "Energy Research and Forecasting" "$dir/CITATION.cff" 2>/dev/null || true; then
            found=1
        fi
    fi

    return $((1 - found))
}

# Function to find ERF repo root
find_erf_dir() {
    # Method 1: Check if we're already in Build/
    if [ -f "../CMakeLists.txt" ] && [ -d "../Source" ]; then
        local candidate="$(cd .. && pwd)"
        if verify_erf_dir "$candidate"; then
            ERF_DIR="$candidate"
            echo "Detected ERF_DIR from Build location: $ERF_DIR"
            return 0
        fi
    fi

    # Method 2: Use git to find repo root
    if command -v git &> /dev/null; then
        if git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then
            local git_root="$(git rev-parse --show-toplevel)"
            if verify_erf_dir "$git_root"; then
                ERF_DIR="$git_root"
                echo "Detected ERF_DIR from git: $ERF_DIR"
                return 0
            fi
        fi
    fi

    # Method 3: Try going up from script location
    local script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    # Check if script is in Build/ directory
    if [[ "$script_dir" =~ /Build$ ]]; then
        local candidate="$(dirname "$script_dir")"
        if verify_erf_dir "$candidate"; then
            ERF_DIR="$candidate"
            echo "Detected ERF_DIR from script location: $ERF_DIR"
            return 0
        fi
    fi

    # Method 4: Check current directory
    if verify_erf_dir "$PWD"; then
        ERF_DIR="$PWD"
        echo "Detected ERF_DIR from current directory: $ERF_DIR"
        return 0
    fi

    return 1
}

# Parse arguments
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
    echo "Usage: $0 <set> [script_pattern] [erf_dir]"
    echo ""
    echo "Sets:"
    echo "  default    - Scripts from Build/"
    echo "  perlmutter - Scripts from Build/Perlmutter/"
    echo "  gnu_ekat   - Scripts from Build/GNU_Ekat/"
    echo ""
    echo "If script_pattern is provided, creates build_<pattern>/"
    echo "Otherwise creates build_<set>/"
    echo ""
    echo "If erf_dir is provided, uses that as ERF_DIR"
    echo "Otherwise auto-detects ERF repo root"
    exit 1
fi

SET=$1
PATTERN=${2:-}
ERF_DIR_ARG=${3:-}

# Set ERF_DIR
if [ -n "$ERF_DIR_ARG" ]; then
    ERF_DIR="$ERF_DIR_ARG"
    echo "Using provided ERF_DIR: $ERF_DIR"
    if ! verify_erf_dir "$ERF_DIR"; then
        echo "Error: Provided directory is not a valid ERF repository"
        echo "Must contain 'Energy Research and Forecasting' in README.rst, LICENSE.md, or CITATION.cff"
        exit 1
    fi
else
    if ! find_erf_dir; then
        echo "Error: Could not auto-detect ERF_DIR"
        echo "Please provide it as the third argument or run from ERF Build/ directory"
        echo ""
        echo "Verification checks for:"
        echo "  - CMakeLists.txt and Source/ directory"
        echo "  - 'Energy Research and Forecasting' in README.rst, LICENSE.md, or CITATION.cff"
        exit 1
    fi
fi

echo "ERF_DIR set to: $ERF_DIR"

# Define source directories relative to ERF_DIR
DEFAULT_DIR="$ERF_DIR/Build"
PERLMUTTER_DIR="$ERF_DIR/Build/Perlmutter"
GNU_EKAT_DIR="$ERF_DIR/Build/GNU_Ekat"

case $SET in
    default)
        SRC_DIR="$DEFAULT_DIR"
        ;;
    perlmutter)
        SRC_DIR="$PERLMUTTER_DIR"
        ;;
    gnu_ekat)
        SRC_DIR="$GNU_EKAT_DIR"
        ;;
    *)
        echo "Error: Invalid set '$SET'"
        echo "Choose: default, perlmutter, or gnu_ekat"
        exit 1
        ;;
esac

if [ ! -d "$SRC_DIR" ]; then
    echo "Error: Source directory does not exist: $SRC_DIR"
    exit 1
fi

# Determine build directory name
if [ -n "$PATTERN" ]; then
    BUILD_DIR="$ERF_DIR/build_${PATTERN}"
else
    BUILD_DIR="$ERF_DIR/build_${SET}"
fi

# Create build directory
mkdir -p "$BUILD_DIR"
echo "Created directory: $BUILD_DIR"

# Find and copy ERF cmake build scripts
echo "Scanning for ERF cmake scripts in $SRC_DIR:"
COPIED=0
SKIPPED=0

# Temporarily disable exit on error for the loop
set +e

for script in "$SRC_DIR"/*.sh; do
    # Check if file exists (glob might not match anything)
    if [ ! -f "$script" ]; then
        continue
    fi

    basename_script=$(basename "$script")

    # Skip backup files
    if [[ "$basename_script" =~ ~$ ]]; then
        SKIPPED=$((SKIPPED + 1))
        continue
    fi

    # Check if it's an ERF cmake script (contains DERF or cmake)
    has_derf=0
    has_cmake=0

    grep -q "DERF" "$script" 2>/dev/null && has_derf=1
    grep -q "cmake" "$script" 2>/dev/null && has_cmake=1

    if [ $has_derf -eq 1 ] || [ $has_cmake -eq 1 ]; then
        cp "$script" "$BUILD_DIR/"
        chmod +x "$BUILD_DIR/$basename_script"
        echo "   DONE: $basename_script"
        COPIED=$((COPIED + 1))
    else
        echo "  ERROR: $basename_script (no DERF or cmake found)"
        SKIPPED=$((SKIPPED + 1))
    fi
done

# Re-enable exit on error
set -e

echo ""
echo "Summary: Copied $COPIED script(s), skipped $SKIPPED"

if [ $COPIED -eq 0 ]; then
    echo "Warning: No ERF cmake scripts found"
    echo "Scripts should contain 'DERF' or 'cmake'"
fi

# Create a run script in the build directory
cat > "$BUILD_DIR/run.sh" << 'EOF'
#!/bin/bash

set -e
set -o pipefail

# Resolve ERF_DIR (go up from build directory)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export ERF_DIR="$(dirname "$SCRIPT_DIR")"

echo "ERF_DIR set to: $ERF_DIR"

# Find all .sh scripts (excluding run.sh and backups)
SCRIPTS=()
for script in *.sh; do
    if [ "$script" = "run.sh" ]; then
        continue
    fi
    if [[ "$script" =~ ~$ ]]; then
        continue
    fi
    if [ -f "$script" ]; then
        SCRIPTS+=("$script")
    fi
done

# Sort scripts alphabetically
IFS=$'\n' SCRIPTS=($(sort <<<"${SCRIPTS[*]}"))
unset IFS

if [ ${#SCRIPTS[@]} -eq 0 ]; then
    echo "Error: No build scripts found in this directory"
    exit 1
fi

if [ $# -ne 1 ]; then
    echo "Usage: $0 <number>"
    echo ""
    echo "Available ERF cmake scripts:"
    for i in "${!SCRIPTS[@]}"; do
        script_base="${SCRIPTS[$i]%.sh}"
        printf "%3d: %s\n" $((i+1)) "${SCRIPTS[$i]}"
        printf "     -> subdirectory: %s/script_%s/\n" "$ERF_DIR" "$script_base"
    done
    echo ""
    echo "Each script will run in its own clean subdirectory at ERF root."
    exit 1
fi

NUM=$1
if [ $NUM -lt 1 ] || [ $NUM -gt ${#SCRIPTS[@]} ]; then
    echo "Error: Number must be between 1 and ${#SCRIPTS[@]}"
    exit 1
fi

SCRIPT="${SCRIPTS[$((NUM-1))]}"
SCRIPT_BASE="${SCRIPT%.sh}"
SUBDIR="$ERF_DIR/script_${SCRIPT_BASE}"

# Create a clean subdirectory for this script
if [ -d "$SUBDIR" ]; then
    echo "Warning: $SUBDIR already exists"
    read -p "Delete and recreate? (y/N) " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        rm -rf "$SUBDIR"
    else
        echo "Aborting. Please remove $SUBDIR manually or choose a different script."
        exit 1
    fi
fi

mkdir -p "$SUBDIR"
echo "========================================"
echo "Running: $SCRIPT"
echo "Build directory: $SUBDIR"
echo "Working directory: $SUBDIR"
echo "========================================"
echo ""

# Copy the script into the subdirectory and run it there
cp "$SCRIPT" "$SUBDIR/"
cd "$SUBDIR"
bash "./$SCRIPT"
EOF

chmod +x "$BUILD_DIR/run.sh"

echo ""
echo "Setup complete!"
echo "Build directory: $BUILD_DIR"
echo ""
echo "To use:"
echo "  cd $BUILD_DIR"
echo "  ./run.sh           # List available scripts"
echo "  ./run.sh <number>  # Run a specific script"
echo ""
echo "Copied $COPIED script(s)"

Usage:

cd Build
./setup_cmake_validation.sh default    # or perlmutter, gnu_ekat
cd ../build_default
./run.sh     # List available scripts
./run.sh 1   # Run first script

Creates isolated build directories for each configuration script. Each script runs in its own subdirectory (script_<name>/) at the ERF root.

Building Documentation

Build ERF documentation with CMake:

cmake -DERF_ENABLE_DOCUMENTATION=ON ..
make docs

This builds both Sphinx and Doxygen documentation.