Repository navigation
GitHub Actions CI: CPU build tests for GNU, Intel OneAPI, and NVHPC #1524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| 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. The container sets PIO; the MPAS Makefile | ||
| # builds with PIO when it is set and with SMIOL when it is not. | ||
| if [ "${{ inputs.use-pio }}" = "true" ]; then | ||
| if [ -z "${PIO}" ]; then | ||
| echo "::error::use-pio is true but PIO is not set by the container environment" | ||
| exit 1 | ||
| fi | ||
| else | ||
| unset PIO | ||
| 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 " PIO: ${PIO:-not set}" | ||
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude suggested this issue. I haven't double checked it myself.
|
||
| 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}" | ||
| 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 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})" | ||
| 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 ====" |
| 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 |
| 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be bumped up a bit? As we're building two CORES now?