diff --git a/.github/workflows/Spack.yml b/.github/workflows/Spack.yml new file mode 100644 index 0000000000..987c236695 --- /dev/null +++ b/.github/workflows/Spack.yml @@ -0,0 +1,75 @@ +# This is a CI workflow for the ufs-weather-model repository that builds the +# modified UWM in a given PR with Spack. +# +# Alex Richert, Mar 2025 + +name: Spack +on: [push,pull_request,workflow_dispatch] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Spack: + strategy: + matrix: + # See ci/package.py for variant definitions. + config: + - { + os: "ubuntu-24.04", + app: "S2SWA", + ccpp_suites: "FV3_GFS_v17_coupled_p8,FV3_GFS_v17_coupled_p8_ugwpv1", + other-variants: ' ^esmf@8.8.0', + } + - { + os: "ubuntu-24.04", + app: "ATM", + ccpp_suites: "FV3_GFS_v16,FV3_GFS_v16_flake,FV3_GFS_v16_ras,FV3_GFS_v17_p8,FV3_GFS_v17_p8_ugwpv1", + other-variants: ' ^esmf@8.8.0', + } + + runs-on: ${{ matrix.config.os }} + permissions: + # For repo-level build caching by NOAA-EMC/ci-test-spack-package action + actions: write + + steps: + + - name: "Install dependencies with APT" + shell: bash + run: | + sudo mv /usr/local /usr/local_mv # relocate so CMake doesn't pick up on bad libraries + sudo apt install libopenmpi-dev openmpi-bin cmake + + - name: "Build Spack package" + uses: NOAA-EMC/ci-test-spack-package@develop + with: + package-name: ufs-weather-model + # ufs-weather-model Spack recipe variants, plus additional package constraints: + package-variants: app=${{ matrix.config.app }} ccpp_suites=${{ matrix.config.ccpp_suites }} ${{ matrix.config.other-variants }} + custom-recipe: ci/package.py + spack-compiler: gcc@13 + spack-test-flag: '' # disable 'make test' for Spack + use-common-build-cache: true + repo-cache-key-suffix: ${{ matrix.config.os }}-1 # Update to create and use a fresh cache + save-repo-cache: ${{ matrix.config.app == 'S2SWA' }} # Only need to save cache from one job + # Set to save to a different cache key than the restored one (in order to add new packages etc.). + # Be sure to unset it after a new cache is generated or a new one will be created each time! + repo-save-key-suffix: '' + + # The recipe-check job validates the Spack recipe by making sure each CMake + # build option is represented. If this check fails due to the addition of a + # new CMake option, ci/package.py should be updated in order to make that + # new build option configurable through Spack. + recipe-check: + + runs-on: ubuntu-24.04 + + steps: + + - name: recipe-check + uses: NOAA-EMC/ci-check-spack-recipe@develop + with: + recipe-file: package/spack/package.py + cmakelists-txt: package/CMakeLists.txt diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index a3d92fc835..e1eb02588e 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -27,7 +27,7 @@ jobs: LINTER_RULES_PATH: '.github/linters/' DEFAULT_BRANCH: origin/develop GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FILTER_REGEX_EXCLUDE: .*(tests/fv3_conf/.*|tests/ci/.*|tests/auto/.*|tests/auto-jenkins/.*|tests/opnReqTests/.*|tests/opnReqTest|tests/atparse.bash).* + FILTER_REGEX_EXCLUDE: .*(tests/fv3_conf/.*|tests/ci/.*|tests/auto/.*|tests/auto-jenkins/.*|tests/opnReqTests/.*|tests/opnReqTest|tests/atparse.bash|ci/package.py).* VALIDATE_BASH: true BASH_SEVERITY: style #VALIDATE_GITHUB_ACTIONS: true diff --git a/ci/README b/ci/README new file mode 100644 index 0000000000..c3dab0a61a --- /dev/null +++ b/ci/README @@ -0,0 +1,3 @@ +This directory contains package.py, a local copy of the ufs-weather-model Spack recipe: https://github.com/spack/spack-packages/blob/develop/repos/spack_repo/builtin/packages/ufs_weather_model/package.py + +It will not need frequent updates, but it is used by the GitHub Actions workflow under .github/workflows/Spack.yml. It only needs updates for changes to minimum version requirements, new dependencies, and new build options. In package.py, `depends_on("esmf@8.8.0:", when="@develop")` for example means that the minimum version that Spack will consider using for UFS Weather Model's ESMF dependency is 8.8.0. These minimum versions should generally track with what is in CMakeLists.txt, though in practice, Spack will typically attempt to use the most recent release of a package available in that package's recipe. diff --git a/ci/package.py b/ci/package.py new file mode 100644 index 0000000000..f792b66d08 --- /dev/null +++ b/ci/package.py @@ -0,0 +1,235 @@ +# Copyright Spack Project Developers. See COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.package import * + + +class UfsWeatherModel(CMakePackage): + """The Unified Forecast System (UFS) Weather Model (WM) is a prognostic + model that can be used for short- and medium-range research and + operational forecasts, as exemplified by its use in the operational Global + Forecast System (GFS) of the National Oceanic and Atmospheric + Administration (NOAA).""" + + homepage = "https://ufs-weather-model.readthedocs.io/en/latest/" + url = "https://github.com/ufs-community/ufs-weather-model/archive/refs/tags/ufs-v1.1.0.tar.gz" + git = "https://github.com/ufs-community/ufs-weather-model.git" + + maintainers("AlexanderRichert-NOAA") + + version("develop", branch="develop", submodules=True) + version( + "2.0.0", + tag="ufs-v2.0.0", + commit="e3cb92f1cd8941c019ee5ef7da5c9aef67d55cf8", + submodules=True, + ) + version( + "1.1.0", + tag="ufs-v1.1.0", + commit="5bea16b6d41d810dc2e45cba0fa3841f45ea7c7a", + submodules=True, + ) + + depends_on("c", type="build") # generated + depends_on("fortran", type="build") # generated + + variant("mpi", default=True, description="Enable MPI") + variant( + "32bit", default=True, description="Enable 32-bit single precision arithmetic in dycore" + ) + variant( + "ccpp_32bit", + default=False, + description="Enable CCPP_32BIT (single precision arithmetic in slow physics)", + ) + variant("debug", default=False, description="Enable DEBUG mode", when="@develop") + variant( + "debug_linkmpi", + default=True, + description="Enable linkmpi option when DEBUG mode is on", + when="@develop", + ) + variant("inline_post", default=False, description="Enable inline post") + variant("multi_gases", default=False, description="Enable multi gases in physics routines") + variant("moving_nest", default=False, description="Enable moving nest code", when="@develop") + variant("openmp", default=True, description="Enable OpenMP") + variant("pdlib", default=False, description="Enable PDLIB (WW3)", when="@develop") + variant("parallel_netcdf", default=True, description="Enable parallel NetCDF") + variant( + "jedi_driver", + default=False, + description="Enable JEDI as top level driver", + when="@develop", + ) + variant( + "cmeps_aoflux", + default=False, + description="Enable atmosphere-ocean flux calculation in mediator", + when="@develop", + ) + variant( + "ccpp", + default=True, + description="Enable the Common Community Physics Package (CCPP)", + when="@:2.0.0", + ) + variant( + "ccpp_suites", + default="FV3_GFS_v15p2,FV3_RRFS_v1alpha", + description="CCPP suites to compile", + values=("FV3_GFS_v15p2", "FV3_RRFS_v1alpha", "FV3_GFS_v15p2,FV3_RRFS_v1alpha"), + multi=True, + when="@:2.0.0", + ) + dev_ccpp_default = [ + "FV3_GFS_v16", + "FV3_GFS_v16_flake", + "FV3_GFS_v17_p8", + "FV3_GFS_v17_p8_rrtmgp", + "FV3_GFS_v15_thompson_mynn_lam3km", + "FV3_WoFS_v0", + "FV3_GFS_v17_p8_mynn", + "FV3_GFS_v17_p8_ugwpv1", + ] + variant( + "ccpp_suites", + default=",".join(dev_ccpp_default), + description="CCPP suites to compile", + multi=True, + when="@develop", + ) + variant( + "quad_precision", + default=False, + description="Enable quad precision for certain grid metric terms in dycore", + when="@:2.0.0", + ) + variant("mom6solo", default=False, description="Build MOM6 solo executable", when="@develop") + + variant("app", default="ATM", description="UFS application", when="@develop") + + depends_on("bacio@:2.4.1") + depends_on("mpi", when="+mpi") + depends_on("netcdf-c") + depends_on("netcdf-fortran") + depends_on("sp") + depends_on("w3emc") + depends_on("esmf@:8.0.0", when="@:2.0.0") + depends_on("nemsio", when="@:2.0.0") + depends_on("w3nco", when="@:2.0.0") + depends_on("bacio@2.4.0:", when="@develop") + depends_on("crtm", when="@develop") + depends_on("esmf@8.8.0:", when="@develop") + depends_on("fms@2022.04: +deprecated_io precision=32,64 constants=GFS", when="@develop") + depends_on("g2", when="@develop") + depends_on("g2tmpl", when="@develop") + depends_on("hdf5+hl+mpi", when="@develop") + depends_on("ip@:4", when="@develop") + depends_on("netcdf-c@4.7.4: ~parallel-netcdf+mpi", when="@develop") + for app in [ + "ATMW", + "ATML", + "NG-GODAS", + "S2S", + "S2SA", + "S2SW", + "S2SWA", + "S2SWAL", + "HAFS", + "HAFSW", + "HAFS-ALL", + "LND", + ]: + depends_on("parallelio@2.5.3: +fortran", when="@develop app=%s" % app) + depends_on("python@3.6:", type="build", when="@develop") + depends_on("sp@2.3.3:", when="@develop") + depends_on("w3emc@2.9.2:", when="@develop") + + with when("@develop app=S2SA"): + depends_on("mapl") + depends_on("gftl-shared") + with when("@develop app=S2SWA"): + depends_on("mapl") + depends_on("gftl-shared") + with when("@develop app=ATMAERO"): + depends_on("mapl") + depends_on("gftl-shared") + depends_on("scotch", when="+pdlib") + + depends_on("w3nco", when="@:2.0.0") + depends_on("python", type="build", when="@:2.0.0") + + conflicts("%gcc@:8", when="@develop") + + def setup_build_environment(self, env): + spec = self.spec + env.set("CC", spec["mpi"].mpicc) + env.set("CXX", spec["mpi"].mpicxx) + env.set("FC", spec["mpi"].mpifc) + env.set("CMAKE_C_COMPILER", spec["mpi"].mpicc) + env.set("CMAKE_CXX_COMPILER", spec["mpi"].mpicxx) + env.set("CMAKE_Fortran_COMPILER", spec["mpi"].mpifc) + + env.set("CCPP_SUITES", ",".join([x for x in spec.variants["ccpp_suites"].value if x])) + + if spec.platform == "linux" and spec.satisfies("%intel"): + env.set("CMAKE_Platform", "linux.intel") + elif spec.platform == "linux" and spec.satisfies("%gcc"): + env.set("CMAKE_Platform", "linux.gnu") + elif spec.platform == "darwin" and spec.satisfies("%gcc"): + env.set("CMAKE_Platform", "macosx.gnu") + else: + msg = "The host system {0} and compiler {0} " + msg += "are not supported by UFS." + raise InstallError(msg.format(spec.platform, self.compiler.name)) + + def cmake_args(self): + args = [ + self.define("AVX2", False), # use target settings from Spack + self.define("SIMDMULTIARCH", False), # use target settings from Spack + self.define_from_variant("CCPP_SUITES", "ccpp_suites").replace(";", ","), + ] + variants = [ + "32bit", + "app", + "ccpp_32bit", + "cmeps_aoflux", + "debug", + "debug_linkmpi", + "inline_post", + "jedi_driver", + "moving_nest", + "mpi", + "multi_gases", + "openmp", + "parallel_netcdf", + "pdlib", + ] + for variant in variants: + args.append(self.define_from_variant(variant.upper(), variant)) + + if self.spec.satisfies("@:2.0.0"): + args.append(self.define_from_variant("CCPP", "ccpp")) + args.append(self.define_from_variant("QUAD_PRECISION", "quad_precision")) + + return args + + # This patch can be removed once https://github.com/NOAA-EMC/WW3/issues/1021 + # is resolved. + @when("+pdlib ^scotch+shared") + def patch(self): + filter_file(r"(lib[^ ]+)\.a", r"\1.so", "WW3/cmake/FindSCOTCH.cmake") + filter_file("STATIC", "SHARED", "WW3/cmake/FindSCOTCH.cmake") + + @run_after("install") + def install_additional_files(self): + mkdirp(prefix.bin) + if self.spec.satisfies("@develop"): + ufs_src = join_path(self.build_directory, "ufs_model") + else: + ufs_src = join_path(self.build_directory, "NEMS.exe") + ufs_dst = join_path(prefix.bin, "ufs_weather_model") + install(ufs_src, ufs_dst) + set_executable(ufs_dst)