Skip to content
Snippets Groups Projects
package.py 9.29 KiB
# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from pathlib import Path

from spack.package import *


class IconNwp(Package):
    """
    Recipe to build ICON-NWP using the LMU gitlab repository.
    """

    git = "ssh://git@gitlab.dkrz.de/icon/icon-nwp.git"
    homepage = "https://code.mpimet.mpg.de/projects/iconpublic"
    # maintainers("oriol.tinto")

    # Version w2w-B6 points to a different repository and branch)
    version("w2w-B6", git="ssh://git@gitlab.physik.uni-muenchen.de/w2w/icon-w2w.git", branch="icon-w2w/icon-nwp-B6")
    # FIXME: The ugly configuration system in older icon versions prevents us from using this package with it.
    # version("2.5.0-nwp3", branch="icon-nwp/op-release-2.5.0-nwp3")
    version("2.6.4-nwp3", branch="icon-nwp/op-release-2.6.4-nwp3")
    version("2.6.5-nwp0", branch="icon-nwp/op-release-2.6.5-nwp0")
    version("master", branch="master")
    version("psp", branch="icon-nwp/icon-nwp-psp")

    variant("lmu", default=False, description="if git.url and submodules should be patched to use the LMU mirrors")
    variant("plexrt", default=False, description="add 3D radiation support with TenStream")
    variant("petsc", default=False, description="add PETSc support")

    # Dependencies
    depends_on("mpi")
    depends_on("netcdf-c")
    depends_on("netcdf-fortran")
    depends_on("eccodes+fortran")
    depends_on("libxml2")

    depends_on("petsc", when="+petsc")
    depends_on("petsc", when="+plexrt")
    depends_on("tenstream", when="+plexrt")

    # Openblas? best way of doing it?
    depends_on("openblas", when="%gcc")
    depends_on("intel-mkl", when="%intel")

    # Extra dependencies for B6, including yaml, and the hdf5 filters for compression.
    depends_on("libyaml", when="@w2w-B6")
    depends_on("hdf5-blosc", when="@w2w-B6")
    depends_on("h5z-zfp", when="@w2w-B6")
    depends_on("sz~hdf5", when="@w2w-B6")
    depends_on("sz3~hdf5", when="@w2w-B6")

    phases = ["configure", "build", "install"]

    def do_fetch(self, mirror_only=False):
        if "+lmu" in self.spec:
            self.fetcher[0].url = self.git = "ssh://git@gitlab.physik.uni-muenchen.de/w2w/icon.git"
        super(IconNwp, self).do_fetch(mirror_only)

    def patch(self):
        # Run git submodule update
        git = which("git")
        if "+lmu" in self.spec:
            self.patch_submodules()
        git("submodule", "update", "--init", "--recursive")

    def patch_submodules(self):
        """
        Because of the lack of access rights to the original submodule repositories,
        we patch the gitmodules file to point to a different mirror.
        """
        git_submodules_file = Path().cwd() / ".gitmodules"

        git_mirror = "git@gitlab.lrz.de:dkrz-mirror"

        git_modules_patch = f"""
        [submodule "externals/mtime"]
                path = externals/mtime
                url = {git_mirror}/libmtime.git
        [submodule "externals/jsbach"]
                path = externals/jsbach
                url = {git_mirror}/jsbach.git
        [submodule "externals/yac"]
                path = externals/yac
                url = {git_mirror}/YAC.git
        [submodule "externals/self"]
                path = externals/self
                url = {git_mirror}/libself.git
        [submodule "externals/tixi"]
                path = externals/tixi
                url = {git_mirror}/libtixi.git
        [submodule "externals/yaxt"]
                path = externals/yaxt
                url = {git_mirror}/yaxt.git
        [submodule "externals/rte-rrtmgp"]
                path = externals/rte-rrtmgp
                url = https://github.com/earth-system-radiation/rte-rrtmgp.git
        [submodule "externals/cub"]
                path = externals/cub
                url = https://github.com/NVlabs/cub.git
        [submodule "externals/omni-xmod-pool"]
                path = externals/omni-xmod-pool
                url = https://github.com/claw-project/omni-xmod-pool.git
        [submodule "externals/cdi"]
                path = externals/cdi
                url = {git_mirror}/libcdi.git
        [submodule "externals/sct"]
                path = externals/sct
                url = {git_mirror}/sct.git
        [submodule "externals/ecrad"]
                path = externals/ecrad
                url = {git_mirror}/libecrad.git
        [submodule "externals/dace_icon"]
                path = externals/dace_icon
                url = {git_mirror}/dace-icon-interface.git
        [submodule "externals/emvorado"]
                path = externals/emvorado
                url = {git_mirror}/emvorado-for-icon.git
        [submodule "utils/mkexp"]
                path = utils/mkexp
                url = https://git.mpimet.mpg.de/public/mkexp
        [submodule "externals/art"]
                path = externals/art
                url = {git_mirror}/art.git
        [submodule "externals/ppm"]
                path = externals/ppm
                url = https://gitlab.dkrz.de/jahns/ppm.git
        [submodule "externals/probtest"]
                path = externals/probtest
                url = {git_mirror}/cscs-sw_probtest.git
        """

        # Replace the content of the original file with the patch
        with git_submodules_file.open("w") as out_f:
            out_f.write(git_modules_patch)

    def setup_build_environment(self, env):
        spec = self.spec

        # Some environment variables to set
        env_variables_to_set = {
            "CC": spec["mpi"].mpicc,
            "FC": spec["mpi"].mpifc,
            "F77": spec["mpi"].mpif77,
        }
        for variable, value in env_variables_to_set.items():
            env.set(variable, value)

    def configure(self, spec, prefix):
        spec = self.spec
        print(spec["mpi"].mpifc)

        libs = [
            f"-L{spec['netcdf-c'].prefix.lib} -lnetcdf ",  # netcdf-c libs
            f"-L{spec['netcdf-fortran'].prefix.lib} -lnetcdff",  # netcdf-fortran libs
            f"-L{spec['eccodes'].prefix.lib} -leccodes_f90 -leccodes",
            f"-L{spec['libxml2'].prefix.lib} -lxml2",  # XML2 libs
        ]

        if self.spec.satisfies("%gcc"):
            libs.append("-lopenblas")
        elif self.spec.satisfies("%intel"):
            libs.append("-qmkl=sequential")

        if self.spec.version == Version("w2w-B6"):
            libs.append("-lyaml")

        mtune = "generic"
        INCLUDES = [f"-I{spec['libxml2'].prefix}/include/libxml2", ]
        CPPFLAGS = []

        if "+plexrt" in self.spec:
            CPPFLAGS.append("-DHAVE_PLEXRT")
            INCLUDES.append(f"-I{spec['tenstream'].prefix}/include")
            libs.append(f"-L{spec['tenstream'].prefix.lib} -ltenstream")

        if "+petsc" in self.spec:
            CPPFLAGS.append("-DHAVE_PETSC")
            INCLUDES.append(f"-I{spec['petsc'].prefix}/include")
            libs.append(f"-L{spec['petsc'].prefix.lib} -lpetsc")


        options = [
            f"CC={spec['mpi'].mpicc}",
            f"FC={spec['mpi'].mpifc}",
            f"CFLAGS=-g -mpc64 {' '.join(INCLUDES)}",
            f"ICON_CFLAGS=-O3 -g -mtune={mtune}",
            f"ICON_BUNDLED_CFLAGS=-O3 -mtune={mtune}",
            f"CPPFLAGS={' '.join(CPPFLAGS)}",
            "FCFLAGS=-std=legacy -fmodule-private -fimplicit-none -fmax-identifier-length=63 -Wall -Wcharacter-truncation -Wconversion -Wunderflow -Wunused-parameter -Wno-surprising -fall-intrinsics -g -mpc64 -w",
            "ICON_FCFLAGS=-fbacktrace -fbounds-check -fstack-protector-all -finit-real=nan -finit-integer=-2147483648 -finit-character=127 -w -O2",
            f"ICON_OCEAN_FCFLAGS=-O3 -mtune={mtune}",
            f"LDFLAGS={' '.join(libs)}",
            f"LIBS={' '.join(libs)}",
            f"--prefix={prefix}",
            f"--enable-grib2",
        ]

        # For some reason there's a problem with OpenMPI with gcc@11.3.0 which makes the configuration fail.
        if self.spec.compiler.name == "gcc" and self.spec.compiler.version == Version("11.3.0"):
            options.append("--enable-mpi-checks=no")
        configure(*options)

    def build(self, spec, prefix):
        make()

    def install(self, spec, prefix):
        make("install")

        # Create extra folders with the data and the docs
        self.make_extra_folders(prefix)

    def setup_run_environment(self, env):
        env.set("ICON_BASE_PATH", self.spec.prefix)
        env.set("ICON_DATA_PATH", self.spec.prefix.join("data"))
        env.set("ICON_DOCS_PATH", self.spec.prefix.join("doc"))

    def make_extra_folders(self, prefix):
        mkdir = which("mkdir")
        rsync = which("rsync")
        curl = which("curl")

        # copy executables and documentation
        mkdir("-p", f"{prefix}/data")
        mkdir("-p", f"{prefix}/doc")
        mkdir("-p", f"{prefix}/run")
        rsync("-av", "data/", f"{prefix}/data")
        rsync("-av", "run/", f"{prefix}/run")
        rsync("-av", "--include='*.pdf'", "--exclude='*.*'", "doc/", f"{prefix}/doc/")
        curl(
            "https://code.mpimet.mpg.de/attachments/download/19568/ICON_tutorial_2019.pdf",
            "--output",
            f"{prefix}/doc/ICON_tutorial_2019.pdf",
        )
        curl(
            "http://www.cosmo-model.org/content/model/documentation/core/emvorado_userguide.pdf",
            "--output",
            f"{prefix}/doc/emvorado_userguide.pdf",
        )