Skip to content
Merged
143 changes: 143 additions & 0 deletions .github/actions/build-mpas/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: 'Build MPAS'
description: 'Build MPAS-Atmosphere with specified compiler configuration'

inputs:
compiler:
description: 'Compiler family (gcc, nvhpc, oneapi, etc.)'
required: true
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'

outputs:
executable:
description: 'Path to built executable'
value: ${{ steps.build.outputs.executable }}

runs:
using: 'composite'
steps:
- name: Build MPAS-A
id: build
shell: bash
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 " 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

# Map COMPILER_FAMILY to make target via ci-config.env lookup
VARNAME="MAKE_TARGET_${COMPILER_FAMILY}"
MAKE_TARGET="${!VARNAME}"
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

EXTRA_MAKE_FLAGS=""
if [ "${COMPILER_FAMILY}" = "nvhpc" ]; then
ARCH="${NVHPC_TARGET_ARCH:--tp=px}"
for FLAG in FFLAGS_OPT CFLAGS_OPT CXXFLAGS_OPT LDFLAGS_OPT; do
if ! sed -n '/^nvhpc:/,/^[a-z]/p' Makefile | grep -qF "\"${FLAG} = "; then
echo "::error::Could not locate ${FLAG} in the NVHPC Makefile target"
exit 1
fi
sed -i "/^nvhpc:/,/^[a-z]/ s/\"${FLAG} = /\"${FLAG} = ${ARCH} /" Makefile
done
EXTRA_MAKE_FLAGS="${NVHPC_EXTRA_MAKE_FLAGS}"
elif [ "${COMPILER_FAMILY}" = "oneapi" ]; then
EXTRA_MAKE_FLAGS="${ONEAPI_EXTRA_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

timeout ${{ inputs.build-timeout }}m \
make ${MAKE_TARGET} CORE=atmosphere ${EXTRA_MAKE_FLAGS} ${PRECISION_FLAG} --jobs ${MAKE_J_PROCS:-$(nproc)}

echo "executable=$(pwd)/atmosphere_model" >> $GITHUB_OUTPUT

- name: Verify executable
shell: bash
run: |
if [ ! -f atmosphere_model ]; then
echo "ERROR: atmosphere_model not found!"
exit 1
fi
ls -la atmosphere_model
file atmosphere_model
97 changes: 97 additions & 0 deletions .github/actions/download-testdata/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: 'Download Test Data'
description: 'Download and extract an MPAS test case archive from GitHub releases'

inputs:
resolution:
description: 'Test case resolution (e.g., 240km, 120km). Used to look up RELEASE_TESTDATA_{RES} in ci-config.env.'
required: true
dest-dir:
description: 'Destination directory name for the extracted test case'
required: false
default: ''

outputs:
case-dir:
description: 'Path to the extracted test case directory'
value: ${{ steps.extract.outputs.case-dir }}

runs:
using: composite
steps:
- name: Resolve release tag and archive
id: resolve
shell: bash
run: |
RESOLUTION="${{ inputs.resolution }}"
ARCHIVE="${RESOLUTION}.tar.gz"

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}"

RES_UPPER=$(echo "${RESOLUTION}" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
TAG_VAR="RELEASE_TESTDATA_${RES_UPPER}"
TAG="${!TAG_VAR}"

if [ -z "${TAG}" ]; then
echo "::error::No release tag for resolution '${RESOLUTION}'. Add ${TAG_VAR}= to ci-config.env."
exit 1
fi

REPO="${DATA_REPOSITORY:-${GITHUB_REPOSITORY}}"
URL="https://github.com/${REPO}/releases/download/${TAG}/${ARCHIVE}"

echo "release-tag=${TAG}" >> $GITHUB_OUTPUT
echo "archive=${ARCHIVE}" >> $GITHUB_OUTPUT
echo "url=${URL}" >> $GITHUB_OUTPUT
echo "Resolved: ${TAG_VAR}=${TAG} → ${URL}"

- name: Cache test case archive
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ${{ steps.resolve.outputs.archive }}
key: testdata-${{ steps.resolve.outputs.release-tag }}

- name: Download test case archive
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
URL="${{ steps.resolve.outputs.url }}"
ARCHIVE="${{ steps.resolve.outputs.archive }}"
echo "Downloading ${ARCHIVE} from ${URL}..."
curl -fsSL --retry 5 --retry-delay 5 "${URL}" -o "${ARCHIVE}"
echo "Downloaded $(du -h "${ARCHIVE}" | cut -f1)"

- name: Extract test case
id: extract
shell: bash
run: |
ARCHIVE="${{ steps.resolve.outputs.archive }}"
DEST="${{ inputs.dest-dir }}"

echo "Cache hit: ${{ steps.cache.outputs.cache-hit }}"
echo "Archive: ${ARCHIVE} ($(du -h "${ARCHIVE}" | cut -f1))"

tar xzf "${ARCHIVE}"

CASE_DIR=$(tar tzf "${ARCHIVE}" 2>/dev/null | head -1 | cut -d/ -f1 || true)
if [ -z "${CASE_DIR}" ]; then
CASE_DIR=$(ls -td */ 2>/dev/null | head -1 | tr -d '/')
fi

if [ -z "${CASE_DIR}" ] || [ ! -d "${CASE_DIR}" ]; then
echo "::error::Failed to extract test case from ${ARCHIVE}"
exit 1
fi

if [ -n "${DEST}" ] && [ "${DEST}" != "${CASE_DIR}" ]; then
mv "${CASE_DIR}" "${DEST}"
CASE_DIR="${DEST}"
fi

echo "case-dir=${CASE_DIR}" >> $GITHUB_OUTPUT
echo "Extracted test case to: ${CASE_DIR}"
100 changes: 100 additions & 0 deletions .github/actions/ect-summary/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: 'ECT Summary'
description: >
Generate a consolidated Ensemble Consistency Test results table from
enriched result files produced by the validate-ect action. Writes a
Markdown table to $GITHUB_STEP_SUMMARY with auto-discovered columns.

inputs:
results-path:
description: 'Directory containing downloaded ect-result-* artifact subdirectories'
required: true

runs:
using: 'composite'
steps:
- name: Generate summary table
shell: bash
run: |
RESULTS_PATH="${{ inputs.results-path }}"

# Collect all result files
RESULT_FILES=()
for f in "${RESULTS_PATH}"/ect-result-*/ect-result.txt; do
[ -f "$f" ] && RESULT_FILES+=("$f")
done

if [ ${#RESULT_FILES[@]} -eq 0 ]; then
echo "::warning::No ECT result files found in ${RESULTS_PATH}"
echo "## Ensemble Consistency Test (ECT) Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "No results available." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

# Discover column names from the first result file (all keys except "result")
COLUMNS=()
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
if [ "$key" != "result" ]; then
COLUMNS+=("$key")
fi
done < "${RESULT_FILES[0]}"

# Build header row
HEADER="| "
SEPARATOR="| "
for col in "${COLUMNS[@]}"; do
COL_TITLE=$(echo "$col" | sed 's/.*/\u&/')
HEADER+="${COL_TITLE} | "
SEPARATOR+="--- | "
done
HEADER+="Result |"
SEPARATOR+="--- |"

# Build data rows
PASS=0 FAIL=0 ERROR=0 SKIP=0 TOTAL=0
ROWS=""
for f in "${RESULT_FILES[@]}"; do
TOTAL=$((TOTAL + 1))

# Parse key=value pairs
declare -A DATA=()
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
DATA["$key"]="$value"
done < "$f"

RESULT="${DATA[result]}"
case "$RESULT" in
PASSED) ICON="PASSED"; PASS=$((PASS + 1)) ;;
FAILED) ICON="**FAILED**"; FAIL=$((FAIL + 1)) ;;
SKIPPED) ICON="SKIPPED"; SKIP=$((SKIP + 1)) ;;
*) ICON="ERROR"; ERROR=$((ERROR + 1)) ;;
esac

ROW="| "
for col in "${COLUMNS[@]}"; do
ROW+="${DATA[$col]:-—} | "
done
ROW+="${ICON} |"
ROWS+="${ROW}"$'\n'

unset DATA
done

SORTED_ROWS=$(echo "$ROWS" | sort)

{
echo "## Ensemble Consistency Test (ECT) Results"
echo ""
echo "$HEADER"
echo "$SEPARATOR"
echo "$SORTED_ROWS"
echo ""
echo "**Total: ${TOTAL}** — ${PASS} passed, ${FAIL} failed, ${ERROR} error, ${SKIP} skipped"

if [ ${FAIL} -gt 0 ] || [ ${ERROR} -gt 0 ]; then
echo ""
echo "> One or more ECT validations failed. Check individual ECT Validate job logs for PyCECT details."
fi
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading