Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions .github/actions/build-mpas/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
name: 'Build MPAS'
description: 'Build MPAS-Atmosphere with specified compiler configuration'

inputs:
compiler:
description: 'Compiler family (gcc, nvhpc, oneapi, etc.)'
required: true
core:
description: 'MPAS core to build (atmosphere, init_atmosphere)'
required: false
default: 'atmosphere'
use-pio:
description: 'Use PIO library (true/false)'
required: false
default: 'false'
openacc:
description: 'Enable OpenACC (true/false)'
required: false
default: 'false'
precision:
description: 'Floating-point precision (single or double)'
required: false
default: 'single'
build-timeout:
description: 'Build timeout in minutes'
required: false
default: '20'
make-target:
description: 'Make target to build. Empty = look up MAKE_TARGET_<compiler> in ci-config.env'
required: false
default: ''
make-flags:
description: 'Extra make arguments for this build (e.g. "OPENMP=true"), added after the compiler defaults from ci-config.env'
required: false
default: ''
use-target-arch:
description: 'NVHPC only: add NVHPC_TARGET_ARCH from ci-config.env to the build flags (true/false). Use false for performance runs.'
required: false
default: 'true'
clean-core:
description: 'Run "make clean" for this core before building (use when a different core was built earlier in the same source tree; empty = no clean)'
required: false
default: ''
log-file:
description: 'Write the build output to this file as well as the job log (empty = no file)'
required: false
default: ''

outputs:
executable:
description: 'Path to built executable (atmosphere_model or init_atmosphere_model)'
value: ${{ steps.build.outputs.executable }}

runs:
using: 'composite'
steps:
- name: Build MPAS-A
id: build
shell: bash
env:
INPUT_MAKE_TARGET: ${{ inputs.make-target }}
INPUT_MAKE_FLAGS: ${{ inputs.make-flags }}
run: |
# Source container environment if available
# This sets NETCDF, PNETCDF, PIO, and other library paths
if [ -f /container/config_env.sh ]; then
echo "Sourcing container environment from /container/config_env.sh"
source /container/config_env.sh
fi

# MPAS Makefile uses `cpp` for Registry preprocessing.
# Some containers (e.g. openSUSE Leap) don't ship it separately.
if ! command -v cpp &>/dev/null; then
echo "cpp not found, installing..."
if command -v zypper &>/dev/null; then
zypper install -y --no-recommends cpp
elif command -v dnf &>/dev/null; then
dnf install -y cpp
elif command -v apt-get &>/dev/null; then
apt-get update && apt-get install -y cpp
fi
fi

# Map compiler input to COMPILER_FAMILY if not already set
# The container should set this, but we provide a fallback
if [ -z "${COMPILER_FAMILY}" ]; then
case "${{ inputs.compiler }}" in
gcc|gfortran|gnu) export COMPILER_FAMILY="gcc" ;;
nvhpc|nvfortran) export COMPILER_FAMILY="nvhpc" ;;
oneapi|intel|ifx) export COMPILER_FAMILY="oneapi" ;;
llvm|flang|clang) export COMPILER_FAMILY="clang" ;;
*) export COMPILER_FAMILY="${{ inputs.compiler }}" ;;
esac
fi

# Set up I/O configuration
if [ "${{ inputs.use-pio }}" = "true" ]; then
export PIO_ROOT=${PIO_ROOT:-/container/pio}
export USE_PIO2=true
else
unset PIO
export USE_PIO2=false
fi

# Set up accelerator configuration
if [ "${{ inputs.openacc }}" = "true" ]; then
export OPENACC=true
fi

echo "Build configuration:"
echo " Core: ${{ inputs.core }}"
echo " Compiler input: ${{ inputs.compiler }}"
echo " COMPILER_FAMILY: ${COMPILER_FAMILY}"
echo " NETCDF: ${NETCDF:-not set}"
echo " PNETCDF: ${PNETCDF:-not set}"
echo " USE_PIO2: ${USE_PIO2}"
echo " OPENACC: ${OPENACC:-false}"
echo " PRECISION: ${{ inputs.precision }}"

# Settings from .github/ci-config.env — edit that file to change targets/workarounds
CI_CONFIG="${GITHUB_WORKSPACE}/.github/ci-config.env"
if [ -f "${CI_CONFIG}" ]; then
source "${CI_CONFIG}"
fi

# Make target: the make-target input, else MAKE_TARGET_<compiler> from ci-config.env
MAKE_TARGET="${INPUT_MAKE_TARGET}"
if [ -z "${MAKE_TARGET}" ]; then
VARNAME="MAKE_TARGET_${COMPILER_FAMILY}"
MAKE_TARGET="${!VARNAME}"
fi
if [ -z "${MAKE_TARGET}" ]; then
echo "::error::No make target for COMPILER_FAMILY=${COMPILER_FAMILY}. Add MAKE_TARGET_${COMPILER_FAMILY}= to .github/ci-config.env"
exit 1
fi

case "${{ inputs.core }}" in
atmosphere) EXE="atmosphere_model" ;;
init_atmosphere) EXE="init_atmosphere_model" ;;
*) echo "::error::Unsupported core '${{ inputs.core }}' (atmosphere, init_atmosphere)"; exit 1 ;;
esac

if [ "${COMPILER_FAMILY}" = "nvhpc" ]; then
ARCH=""
if [ "${{ inputs.use-target-arch }}" = "true" ]; then
ARCH="${NVHPC_TARGET_ARCH}"
fi
if [ -n "${ARCH}" ]; then
for FLAG in FFLAGS_OPT CFLAGS_OPT CXXFLAGS_OPT LDFLAGS_OPT; do
if ! sed -n "/^${MAKE_TARGET}:/,/^[a-z]/p" Makefile | grep -qF "\"${FLAG} = "; then
echo "::error::Could not locate ${FLAG} in the '${MAKE_TARGET}' Makefile target"
exit 1
fi
# Skip if already applied (the action can run twice in one source tree)
if sed -n "/^${MAKE_TARGET}:/,/^[a-z]/p" Makefile | grep -qF "\"${FLAG} = ${ARCH} "; then
continue
fi
sed -i "/^${MAKE_TARGET}:/,/^[a-z]/ s/\"${FLAG} = /\"${FLAG} = ${ARCH} /" Makefile
done
fi
fi

# Compiler defaults from ci-config.env (<FAMILY>_EXTRA_MAKE_FLAGS, family in
# upper case), then the make-flags input
FLAGS_VAR="${COMPILER_FAMILY^^}_EXTRA_MAKE_FLAGS"
EXTRA_MAKE_FLAGS="${!FLAGS_VAR}"
if [ -n "${INPUT_MAKE_FLAGS}" ]; then
EXTRA_MAKE_FLAGS="${EXTRA_MAKE_FLAGS:+${EXTRA_MAKE_FLAGS} }${INPUT_MAKE_FLAGS}"
fi

echo " Make target: ${MAKE_TARGET}"
echo " Extra make flags: ${EXTRA_MAKE_FLAGS:-<none>}"
echo " Parallel jobs: ${MAKE_J_PROCS:-$(nproc)}"

PRECISION_FLAG=""
if [ "${{ inputs.precision }}" = "double" ]; then
PRECISION_FLAG="PRECISION=double"
fi

CLEAN_CMD=""
if [ -n "${{ inputs.clean-core }}" ]; then
CLEAN_CMD="make clean CORE=${{ inputs.clean-core }}"
fi
MAKE_CMD="timeout ${{ inputs.build-timeout }}m make ${MAKE_TARGET} CORE=${{ inputs.core }} ${EXTRA_MAKE_FLAGS} ${PRECISION_FLAG} --jobs ${MAKE_J_PROCS:-$(nproc)}"

LOG_FILE="${{ inputs.log-file }}"
set -o pipefail
# Tool versions and the exact commands (job log + log file)
PRINT_ENV="${GITHUB_WORKSPACE}/.github/actions/build-mpas/print-build-env.sh"
if [ ! -f "${PRINT_ENV}" ]; then
echo "::error::${PRINT_ENV} not found"
exit 1
fi
export COMPILER_FAMILY MAKE_TARGET ARCH CLEAN_CMD MAKE_CMD
CORE="${{ inputs.core }}" USE_PIO="${{ inputs.use-pio }}" PRECISION="${{ inputs.precision }}" \
bash "${PRINT_ENV}" | tee "${LOG_FILE:-/dev/null}"
if [ -n "${CLEAN_CMD}" ]; then
${CLEAN_CMD} 2>&1 | tee -a "${LOG_FILE:-/dev/null}"
fi
${MAKE_CMD} 2>&1 | tee -a "${LOG_FILE:-/dev/null}"

echo "executable=$(pwd)/${EXE}" >> "$GITHUB_OUTPUT"

- name: Verify executable
shell: bash
run: |
EXE="${{ steps.build.outputs.executable }}"
if [ ! -f "${EXE}" ]; then
echo "::error::${EXE} not found"
exit 1
fi
ls -la "${EXE}"
file "${EXE}"
60 changes: 60 additions & 0 deletions .github/actions/build-mpas/print-build-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Print the tool versions and exact commands for an MPAS build.
# Called by action.yml; the output goes to the job log and the build log.
#
# Environment variables set by the action:
# COMPILER_FAMILY gcc, oneapi, nvhpc, ...
# MAKE_TARGET make target
# CORE atmosphere or init_atmosphere
# USE_PIO true or false
# PRECISION single or double
# ARCH NVHPC target arch flag that was applied (empty = none)
# CLEAN_CMD clean command run before the build (empty = none)
# MAKE_CMD the build command
# NETCDF, PNETCDF and PIO_ROOT come from the container environment.

# First non-empty line of a command's output, or "not found"
v() { command -v "$1" >/dev/null 2>&1 || { echo "not found"; return 0; }; { "$@" 2>&1 || true; } | grep -m1 . || true; }
case "${COMPILER_FAMILY}" in
gcc) FC_SER=gfortran; CC_SER=gcc; FC_MPI=mpif90 ;;
oneapi) FC_SER=ifx; CC_SER=icx; FC_MPI=mpifort ;;
nvhpc) FC_SER=nvfortran; CC_SER=nvc; FC_MPI=mpifort ;;
*) FC_SER=mpifort; CC_SER=mpicc; FC_MPI=mpifort ;;
esac
PATH="${NETCDF:+${NETCDF}/bin:}${PNETCDF:+${PNETCDF}/bin:}${PATH}"
# MPI implementation: MPICH or Open MPI
if command -v mpichversion >/dev/null 2>&1; then
MPI_VERSION="$(v mpichversion)"; MPI_SHOW="-show"
elif command -v ompi_info >/dev/null 2>&1; then
MPI_VERSION="$(v ompi_info --version)"; MPI_SHOW="--showme"
else
MPI_VERSION="not found"; MPI_SHOW="-show"
fi
echo "==== Build environment ===="
echo "Core: ${CORE} (${COMPILER_FAMILY}, make target ${MAKE_TARGET})"
echo "Fortran: $(v ${FC_SER} --version)"
echo "C: $(v ${CC_SER} --version)"
echo "MPI: ${MPI_VERSION}"
echo "MPI wrapper: $(v ${FC_MPI} ${MPI_SHOW} | cut -c1-160)"
echo "NetCDF-C: $(v nc-config --version)"
echo "NetCDF-Fortran: $(v nf-config --version)"
echo "PnetCDF: $(v pnetcdf-config --version)"
if [ "${USE_PIO}" = "true" ]; then
echo "I/O layer: PIO (${PIO_ROOT})"
else
echo "I/O layer: SMIOL (in-tree)"
fi
echo "Precision: ${PRECISION}"
echo "Container OS: $( (. /etc/os-release && echo "${PRETTY_NAME}") 2>/dev/null || echo unknown)"
if [ "${COMPILER_FAMILY}" = "nvhpc" ]; then
if [ -n "${ARCH}" ]; then
echo "Makefile edit: added ${ARCH} to FFLAGS_OPT, CFLAGS_OPT, CXXFLAGS_OPT, LDFLAGS_OPT"
else
echo "Makefile edit: none (use-target-arch is false)"
fi
fi
if [ -n "${CLEAN_CMD}" ]; then
echo "Clean command: ${CLEAN_CMD}"
fi
echo "Build command: ${MAKE_CMD}"
echo "==== End build environment ===="
65 changes: 65 additions & 0 deletions .github/actions/resolve-container/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: 'Resolve Container Image'
description: 'Assemble a container image name from ci-config.env templates'

inputs:
compiler:
description: 'Compiler family (gcc, nvhpc, oneapi)'
required: true
mpi:
description: 'MPI implementation (openmpi, mpich)'
required: true
gpu:
description: 'GPU variant (empty string, or "cuda")'
required: false
default: ''

outputs:
image:
description: 'Fully qualified container image name'
value: ${{ steps.resolve.outputs.image }}

runs:
using: 'composite'
steps:
- name: Resolve container image
id: resolve
shell: bash
run: |
CI_CONFIG="${GITHUB_WORKSPACE}/.github/ci-config.env"
if [ ! -f "${CI_CONFIG}" ]; then
echo "::error::ci-config.env not found at ${CI_CONFIG}"
exit 1
fi
source "${CI_CONFIG}"

COMPILER="${{ inputs.compiler }}"
MPI="${{ inputs.mpi }}"
GPU="${{ inputs.gpu }}"

# Resolve compiler name (fallback to raw value if no mapping)
comp_var="CONTAINER_COMPILER_${COMPILER}"
COMPILER_NAME="${!comp_var:-$COMPILER}"

# Resolve MPI name (fallback to raw value if no mapping)
mpi_var="CONTAINER_MPI_${MPI}"
MPI_NAME="${!mpi_var:-$MPI}"

# Pick template: GPU vs CPU, then check for per-compiler override
if [ -n "${GPU}" ]; then
gpu_override="CONTAINER_IMAGE_GPU_${COMPILER}"
TEMPLATE="${!gpu_override:-$CONTAINER_IMAGE_GPU}"
else
cpu_override="CONTAINER_IMAGE_${COMPILER}"
TEMPLATE="${!cpu_override:-$CONTAINER_IMAGE}"
fi

# Substitute placeholders
IMAGE="${TEMPLATE//\{compiler\}/$COMPILER_NAME}"
IMAGE="${IMAGE//\{mpi\}/$MPI_NAME}"

echo "Resolved container image: ${IMAGE}"
echo " Compiler: ${COMPILER} → ${COMPILER_NAME}"
echo " MPI: ${MPI} → ${MPI_NAME}"
echo " GPU: ${GPU:-none}"

echo "image=${IMAGE}" >> $GITHUB_OUTPUT
47 changes: 47 additions & 0 deletions .github/ci-config.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# .github/ci-config.env — Central CI configuration
#
# Sourced by workflows and composite actions. Edit here to change
# containers, compilers, or build workarounds.


# ── Container images ──────────────────────────────
# {compiler} and {mpi} are replaced by resolve-container.
# Tags: https://hub.docker.com/r/ncarcisl/hpcdev-x86_64/tags

CONTAINER_IMAGE="docker.io/ncarcisl/hpcdev-x86_64:almalinux9-{compiler}-{mpi}-26.02"

# Per-compiler override: CONTAINER_IMAGE_{compiler}="template"


# ── Container name mappings ───────────────────────
# Map CI compiler/MPI names to Docker Hub tag strings when they differ.

CONTAINER_COMPILER_gcc=gcc14
# CONTAINER_MPI_mpich=mpich3


# ── Makefile target mapping ───────────────────────
# MAKE_TARGET_{compiler} → MPAS Makefile target name.

MAKE_TARGET_gcc=gfortran
MAKE_TARGET_nvhpc=nvhpc
MAKE_TARGET_oneapi=intel
MAKE_TARGET_clang=llvm


# ── Compiler workarounds ─────────────────────────

# Target arch flag added to the NVHPC build flags. -tp=px is portable
# (CI workflows can sometimes build and run on different hosts). A build
# turns it off with use-target-arch: 'false' on the build-mpas action.
NVHPC_TARGET_ARCH=-tp=px

# Extra make arguments for every build with a compiler:
# <FAMILY>_EXTRA_MAKE_FLAGS, with the family in upper case
# (GCC, NVHPC, ONEAPI, CLANG). A single build can add more with
# the make-flags input on the build-mpas action.
#
# Disable MPI Fortran 2008 bindings (not supported by MPICH
# builds in current containers).
NVHPC_EXTRA_MAKE_FLAGS="MPAS_MPI_F08=0"
ONEAPI_EXTRA_MAKE_FLAGS="MPAS_MPI_F08=0"
Loading