diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66792a6b..b81d1192 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,13 +1,14 @@ # Contributors -| GitHub user | Real Name | Affiliation | Date | -| --------------- | ---------------- | ----------- | ---------- | -| james-bruten-mo | James Bruten | Met Office | 2025-12-09 | -| ppharris | Phil Harris | UKCEH | 2025-12-18 | -| maggiehendry | Maggie Hendry | Met Office | 2026-01-26 | -| andrewcoughtrie | Andrew Coughtrie | Met Office | 2026-02-10 | -| yaswant | Yaswant Pradhan | Met Office | 2026-02-11 | -| ScottWales | Scott Wales | Bureau of Meteorology | 2026-02-16 | -| t00sa | Sam Clarke-Green | Met Office | 2026-02-27 | -| eleanorgb | Eleanor Burke | Met Office | 2026-03-06 | -| Pierre-siddall | Pierre Siddall| Met Office | 2026-02-06 | -| tinyendian | Wolfgang Hayek | Earth Sciences New Zealand | 2026-04-24 | +| GitHub user | Real Name | Affiliation | Date | +| --------------- | ---------------- | -------------------------- | ---------- | +| james-bruten-mo | James Bruten | Met Office | 2025-12-09 | +| ppharris | Phil Harris | UKCEH | 2025-12-18 | +| maggiehendry | Maggie Hendry | Met Office | 2026-01-26 | +| andrewcoughtrie | Andrew Coughtrie | Met Office | 2026-02-10 | +| yaswant | Yaswant Pradhan | Met Office | 2026-02-11 | +| ScottWales | Scott Wales | Bureau of Meteorology | 2026-02-16 | +| t00sa | Sam Clarke-Green | Met Office | 2026-02-27 | +| eleanorgb | Eleanor Burke | Met Office | 2026-03-06 | +| Pierre-siddall | Pierre Siddall | Met Office | 2026-02-06 | +| tinyendian | Wolfgang Hayek | Earth Sciences New Zealand | 2026-04-24 | +| hiker | Joerg Henrichs | Bureau of Meteorology | 2026-02-11 | diff --git a/fab/fab_jules.py b/fab/fab_jules.py new file mode 100755 index 00000000..ff4bf06c --- /dev/null +++ b/fab/fab_jules.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# For further details please refer to the file COPYRIGHT +# which you should have received as part of this distribution +############################################################################## + +''' +This module contains a BAF-based build script for Jules. +''' + +import argparse +import logging +from pathlib import Path +import sys +from typing import cast, Iterable, Optional, Union + + +from fab.api import (FabBase, fcm_export, Exclude, find_source_files, + git_checkout, Include, root_inc_files) + + +class JulesBuild(FabBase): + ''' + A class to build Jules using BAF as base class. + + :param str name: name of the build. + ''' + + def __init__(self, name: str): + ''' + Build Jules using Fab. + + :name: The base name to use for the project directory + ''' + # If the sources need to be checked out or not + self._checkout = False + + # The revision number to check out (if requested) + self._revision = None + + # The root of the jules repository. Defined in + # handle_command_line_options + self._root = None + + super().__init__(name) + + def define_command_line_options( + self, + parser: Optional[argparse.ArgumentParser] = None + ) -> argparse.ArgumentParser: + ''' + This adds a revision option to the command line options inherited + from the base class. + + :param Optional[argparse.ArgumentParser] parser: a pre-defined + argument parser. If not, a new instance will be created. + :returns: the argument parser with the jules specific options added. + :rtype :py:class:`argparse.ArgumentParser` + ''' + + parser = super().define_command_line_options(parser) + parser = cast(argparse.ArgumentParser, parser) + parser.add_argument( + "--revision", "-r", type=str, default="vn7.8", + help="Sets the Jules revision to checkout (only used if" + "--checkout is used). Defaults to 'vn7.8'.") + parser.add_argument( + "--checkout", default=False, action="store_true", + help="If specified, will checkout jules from git or svn, " + "otherwise this script is excepted to be in a cloned " + "git version.") + parser.add_argument( + "--rivers", default=False, action="store_true", + help="If specified, the stand-alone rivers binary will be " + "compiled.") + parser.add_argument( + "--ascii-out", default=False, action="store_true", + help="If specified, NetCDF will be disabled and output " + "will be in ASCII instead.") + return parser + + def handle_command_line_options(self, + parser: argparse.ArgumentParser) -> None: + ''' + Grab the requested (or default) arguments for checkout and Jules + revision to use and store it in an attribute. Do consistency checks + to make sure a revision is only specified if also a checkout is + requested. + + :param parser: the argument parser. + + :raises ValueError: if a revision is specified, but no checkout + is requested. + ''' + + super().handle_command_line_options(parser) + self._checkout = self.args.checkout + self._revision = self.args.revision + self._ascii_out = self.args.ascii_out + self._rivers = self.args.rivers + if self._rivers: + self.set_root_symbol("river") + + if not self._checkout and "--revision" in sys.argv[1:]: + raise ValueError(f"You specified revision '{self._revision}', " + f"but did not request a checkout.") + + def define_project_name(self, name: str) -> str: + ''' + This method adds version number, ascii output (if selected), and + MPI and OpenMP information to the project name, so these different + binaries can be distinguished. + + :returns: the project directory name to use. + ''' + if self._rivers: + name = name + "-rivers" + if self._revision: + name = name + f"-{self._revision}" + if self.args.ascii_out: + name = name + "-ascii" + if self.args.mpi: + name = name + "-mpi" + if self.args.openmp: + name = name + "-openmp" + return super().define_project_name(name) + + def grab_files_step(self) -> None: + ''' + Extracts all the required Jules source files from the repositories. + + :raises RuntimeError: if no checkout is required, but expected Jules + directory (rose-meta/jules-shared) does not exist, indicating + an invalid directory structure. + ''' + # If no checkout was requested, make sure we have the expected + # repository structure. + if not self._checkout: + # Get the root directory of this Jules: + self._root = Path(__file__).resolve().parents[1] + jules_shared = self._root / "rose-meta" / "jules-shared" + if not jules_shared.exists(): + raise RuntimeError(f"The expected directory '{jules_shared}' " + f"does not exist.") + return + + # Try to grab sources from GitHub, fallback to FCM if that fails + + try: + git_checkout( + self.config, + src="git@github.com:MetOffice/jules", + revision=self._revision, + dst_label="jules.git", + ) + self._root = (self.config.project_workspace + / "source" / "jules.git") + except Exception as e: + logging.warning(f"git_checkout failed: {e}, " + f"falling back to fcm_export") + # We export the whole svn repository, to be consistent with + # using either a local checkout or git checkout + fcm_export( + self.config, + src="fcm:jules.xm_tr", + revision=self._revision, + dst_label="jules.svn", + ) + self._root = self.config.project_workspace / "source" / "jules.svn" + + def find_source_files_step( + self, + path_filters: Optional[Iterable[Union[Exclude, Include]]] = None): + ''' + Finds all the Jules sources files to analyse. + + :param path_filters: optional list of path filters to be passed to + Fab find_source_files, default is None. + ''' + if path_filters: + local_filters = path_filters.copy() + else: + local_filters = [] + local_filters.extend([ + Exclude("src/control/um/"), + Exclude("src/initialisation/um/"), + Exclude("src/params/shared/cable_maths_constants_mod.F90"), + ] + ) + if self._rivers: + # The order is important, the last include/exclude statement + # takes precedence. So exclude standalone must come before + # including init_initial_mod + local_filters.extend([ + Exclude("src/control/standalone/jules.F90"), + Exclude("src/initialisation/standalone/"), + Include("src/initialisation/standalone/init_initial_mod.F90"), + Include("src/initialisation/standalone/initial_conditions/" + "jules_initial_mod.F90"), + Include("src/initialisation/standalone/init_output_mod.F90"), + Include("src/initialisation/standalone/init_rivers.F90"), + Include("src/initialisation/standalone/init_time_mod.F90"), + Include("src/initialisation/standalone/init_drive_mod.F90"), + Include("src/initialisation/standalone/" + "init_model_environment_mod.F90"), + Include("src/initialisation/standalone/grid/" + "fill_model_grid_arrays_mod.F90"), + Include("src/initialisation/standalone/grid/" + "init_input_grid_mod.F90"), + Include("src/initialisation/standalone/grid/" + "init_river_out_grid_mod.F90"), + Include("src/initialisation/standalone/grid/" + "init_latlon_mod.F90"), + Include("src/initialisation/standalone/grid/" + "init_land_frac_mod.F90"), + Include("src/initialisation/standalone/grid/" + "init_model_grid_mod.F90"), + Include("src/initialisation/standalone/rivers-standalone/" + "ancillaries"), + Include("src/initialisation/standalone/ancillaries/" + "init_rivers_props_mod.F90"), + Include("src/initialisation/standalone/ancillaries/" + "ancil_namelist_mod.F90"), + Include("src/initialisation/standalone/ancillaries/" + "init_ancillaries_coupling_mod.F90"), + Include("src/initialisation/standalone/ancillaries/" + "init_rivers_process_data_mod.F90"), + Include("src/initialisation/standalone/ancillaries/" + "jules_overbank_props_mod.F90"), + Include("src/initialisation/standalone/ancillaries/" + "jules_rivers_props_mod.F90"), + Exclude("src/initialisation/shared/" + "check_compatible_options_mod.F90"), + Exclude("src/control/lfric/check_unavailable_options_mod.F90"), + Exclude("src/io/dump/read_dump_mod.F90"), + Exclude("src/io/dump/write_dump_mod.F90"), + ]) + else: + local_filters.extend([ + Exclude("src/control/rivers-standalone/"), + Exclude("src/io/rivers-standalone"), + Exclude("src/initialisation/rivers-standalone/"), + Exclude("src/control/lfric/check_unavailable_options_mod.F90"), + ]) + + find_source_files(self.config, + source_root=self._root / "src", + path_filters=local_filters) + + # Add the utility files as required + # --------------------------------- + utils = self._root / "utils" + # For now assume dr hook is always disabled, so use dummy + find_source_files(self.config, + source_root=utils / "drhook_dummy") + if not self.config.mpi: + find_source_files(self.config, + source_root=utils / "mpi_dummy") + if self._ascii_out: + find_source_files(self.config, + source_root=utils / "netcdf_dummy") + + # move inc files to the root for easy tool use + root_inc_files(self.config) + + def define_preprocessor_flags_step(self) -> None: + ''' + Defines the preprocessor flags. + ''' + super().define_preprocessor_flags_step() + flags = ["-I$output"] + if not self.config.mpi: + flags.append("-DMPI_DUMMY") + if self._ascii_out: + flags.append("-DNCDF_DUMMY") + + self.add_preprocessor_flags(flags) + + def get_linker_flags(self) -> list[str]: + ''' + Base class for setting linker flags. + :returns: list of flags for the linker. + ''' + libs = [] + if not self._ascii_out: + libs.extend(["netcdf", "hdf5"]) + return libs + + +if __name__ == "__main__": + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + class NoFabFilter(logging.Filter): + '''A dummy class that disables all Fab noise. + ''' + def filter(self, record): + return not record.name.startswith("fab") + + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) + last_resort_handler = logging.lastResort + last_resort_handler.setLevel(logging.DEBUG) + + baf_logger = logging.getLogger("baf") + baf_logger.setLevel(logging.DEBUG) + fab_logger = logging.getLogger("fab") + fab_logger.setLevel(logging.WARNING) + fab_handlers = fab_logger.handlers + # fab_handlers[0].addFilter(NoFabFilter()) + + jb = JulesBuild("jules") + jb.build() diff --git a/fab/site_specific/default/__init__.py b/fab/site_specific/default/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fab/site_specific/default/config.py b/fab/site_specific/default/config.py new file mode 100644 index 00000000..b15f1a11 --- /dev/null +++ b/fab/site_specific/default/config.py @@ -0,0 +1,153 @@ +#! /usr/bin/env python3 + + +''' +This module contains the default Baf configuration class. +''' + +import argparse +from typing import List + +from fab.api import BuildConfig, Category, ToolRepository + +from default.setup_script_cray import setup_script_cray +from default.setup_script_gnu import setup_script_gnu +from default.setup_script_intel_classic import setup_script_intel_classic +from default.setup_script_intel_llvm import setup_script_intel_llvm +from default.setup_script_nvidia import setup_script_nvidia + + +class Config: + ''' + This class is the default Configuration object for Baf builds. + It provides several callbacks which will be called from the build + scripts to allow site-specific customisations. + ''' + + def __init__(self): + self._args = None + + @property + def args(self) -> argparse.Namespace: + ''' + :returns argparse.Namespace: the command line options specified by + the user. + ''' + return self._args + + def get_valid_profiles(self) -> List[str]: + ''' + Determines the list of all allowed compiler profiles. The first + entry in this list is the default profile to be used. This method + can be overwritten by site configs to add or modify the supported + profiles. + + :returns List[str]: list of all supported compiler profiles. + ''' + return ["full-debug", "fast-debug", "production", "unit-tests"] + + def update_toolbox(self, build_config: BuildConfig) -> None: + ''' + Set the default compiler flags for the various compiler + that are supported. + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + # First create the default compiler profiles for all available + # compilers. While we have a tool box with exactly one compiler + # in it, compiler wrappers will require more than one compiler + # to be initialised - so we just initialise all of them (including + # the linker): + tr = ToolRepository() + for compiler in (tr[Category.C_COMPILER] + + tr[Category.FORTRAN_COMPILER] + + tr[Category.LINKER]): + if compiler.is_available: + # Define a base profile, which contains the common + # compilation flags. This 'base' is not accessible to + # the user, so it's not part of the profile list. Also, + # make it inherit from the default profile '', so that + # a user does not have to specify the base profile + compiler.define_profile("base", inherit_from="") + for profile in self.get_valid_profiles(): + compiler.define_profile(profile, inherit_from="base") + + self.setup_intel_classic(build_config) + self.setup_intel_llvm(build_config) + self.setup_gnu(build_config) + self.setup_nvidia(build_config) + self.setup_cray(build_config) + + def handle_command_line_options(self, args: argparse.Namespace) -> None: + ''' + Additional callback function executed once all command line + options have been added. This is for example used to add + Vernier profiling flags, which are site-specific. + + :param argparse.Namespace args: the command line options added in + the site configs + ''' + # Keep a copy of the args, so they can be used when + # initialising compilers + self._args = args + + def setup_cray(self, build_config: BuildConfig) -> None: + ''' + This method sets up the Cray compiler and linker flags. + For now call an external function, since it is expected that + this configuration can be very lengthy (once we support + compiler modes). + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + setup_script_cray(build_config, self.args) + + def setup_gnu(self, build_config: BuildConfig) -> None: + ''' + This method sets up the Gnu compiler and linker flags. + For now call an external function, since it is expected that + this configuration can be very lengthy (once we support + compiler modes). + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + setup_script_gnu(build_config, self.args) + + def setup_intel_classic(self, build_config: BuildConfig) -> None: + ''' + This method sets up the Intel classic compiler and linker flags. + For now call an external function, since it is expected that + this configuration can be very lengthy (once we support + compiler modes). + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + setup_script_intel_classic(build_config, self.args) + + def setup_intel_llvm(self, build_config: BuildConfig) -> None: + ''' + This method sets up the Intel LLVM compiler and linker flags. + For now call an external function, since it is expected that + this configuration can be very lengthy (once we support + compiler modes). + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + setup_script_intel_llvm(build_config, self.args) + + def setup_nvidia(self, build_config: BuildConfig) -> None: + ''' + This method sets up the Nvidia compiler and linker flags. + For now call an external function, since it is expected that + this configuration can be very lengthy (once we support + compiler modes). + + :param build_config: the Fab build configuration instance + :type build_config: :py:class:`fab.BuildConfig` + ''' + setup_script_nvidia(build_config, self.args) diff --git a/fab/site_specific/default/setup_script_cray.py b/fab/site_specific/default/setup_script_cray.py new file mode 100644 index 00000000..f87a6509 --- /dev/null +++ b/fab/site_specific/default/setup_script_cray.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +''' +This file contains a function that sets the default flags for the Cray +compilers and linkers in the ToolRepository. + +This function gets called from the default site-specific config file +''' + +import argparse +from typing import cast + +from fab.api import BuildConfig, Category, Compiler, Linker, ToolRepository + + +def setup_script_cray(build_config: BuildConfig, args: argparse.Namespace): + # pylint: disable=unused-argument + ''' + Defines the default flags for ftn. + + :param build_config: the Fab build config instance from which + required parameters can be taken. + :type build_config: :py:class:`fab.BuildConfig` + :param argparse.Namespace args: all command line options + ''' + + tr = ToolRepository() + ftn = tr.get_tool(Category.FORTRAN_COMPILER, "crayftn-ftn") + ftn = cast(Compiler, ftn) + + if not ftn.is_available: + return + + # The base flags + # ============== + flags = ["-g", "-G0", "-m", "0", # ? + "-M", "E664,E7208,E7212", # ? + "-en", # Fortran standard + "-ef", # use lowercase module names!Important! + "-hnocaf", # Required for linking with C++ + ] + + # Handle accelerator options: + if args.openacc or args.openmp: + host = args.host.lower() + else: + # Neither openacc nor openmp specified + host = "" + + if args.openacc: + if host == "gpu": + flags.extend(["-h acc"]) + else: + # CPU + flags.extend(["-h acc"]) + elif args.openmp: + if host == "gpu": + flags.extend([]) + else: + # OpenMP on CPU, that's already handled by Fab + pass + + ftn.add_flags(flags, "base") + + # Full debug + # ========== + ftn.add_flags(["-Ktrap=fp", # floating point checking + "-R", "bcdps", # bounds, array shape, collapse, + # pointer, string checking + "-O0"], # No optimisation + "full-debug") + if ftn.get_version() >= (15, 0): + ftn.add_flags(["-G0"], "full-debug") + else: + ftn.add_flags(["-Gfast"], "full-debug") + + # Fast debug + # ========== + ftn.add_flags(["-O2"], "fast-debug") + if ftn.get_version() >= (15, 0): + ftn.add_flags(["-G2"], "fast-debug") + else: + ftn.add_flags(["-Gfast"], "fast-debug") + + # Production + # ========== + ftn.add_flags(["-O3"], "production") + + # Set up the linker + # ================= + linker = tr.get_tool(Category.LINKER, "linker-crayftn-ftn") + linker = cast(Linker, linker) + + # ATM we don't use a shell when running a tool, and as such + # we can't directly use "$()" as parameter. So query these values using + # Fab's shell tool (doesn't really matter which shell we get, so just + # ask for the default): + shell = tr.get_default(Category.SHELL) + + try: + # We must remove the trailing new line, and create a list: + nc_flibs = shell.run(additional_parameters=["-c", "nf-config --flibs"], + capture_output=True).strip().split() + except RuntimeError: + nc_flibs = [] + + linker.add_lib_flags("netcdf", nc_flibs) + linker.add_lib_flags("yaxt", ["-lyaxt", "-lyaxt_c"]) + linker.add_lib_flags("xios", ["-lxios"]) + linker.add_lib_flags("hdf5", ["-lhdf5"]) + + linker.add_post_lib_flags("-lcraystdc++") + + # Using the GNU compiler on Crays for now needs the additional + # flag -fallow-argument-mismatch to compile mpi_mod.f90 + ftn = tr.get_tool(Category.FORTRAN_COMPILER, "crayftn-gfortran") + ftn.add_flags("-fallow-argument-mismatch") diff --git a/fab/site_specific/default/setup_script_gnu.py b/fab/site_specific/default/setup_script_gnu.py new file mode 100644 index 00000000..318e2351 --- /dev/null +++ b/fab/site_specific/default/setup_script_gnu.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +'''This file contains a function that sets the default flags for all +GNU based compilers in the ToolRepository. + +This function gets called from the default site-specific config file +''' + +import argparse + +from typing import cast +from fab.api import BuildConfig, Category, Linker, ToolRepository + + +def setup_script_gnu(build_config: BuildConfig, args: argparse.Namespace): + # pylint: disable=unused-argument + '''Defines the default flags for all GNU compilers. + + :para build_config: the build config from which required parameters + can be taken. + :param args: all command line options + ''' + + tr = ToolRepository() + gfortran = tr.get_tool(Category.FORTRAN_COMPILER, "gfortran") + + if not gfortran.is_available: + gfortran = tr.get_tool(Category.FORTRAN_COMPILER, "mpif90-gfortran") + if not gfortran.is_available: + return + + # The base flags + # ============== + gfortran.add_flags( + ['-ffree-line-length-none', '-Wall', + '-g', + # TODO: Jules river cannot be compiled with this: + # '-Werror=unused-value', + # We might either try to fix the sources if possible, + # or see if it's worth setting this option only for one + # specific file (though that then means compiler-specific + # options in the generic fab script :( ) + '-Werror=tabs', + '-std=f2008', + ], + "base") + + if gfortran.get_version() >= (10, 0): + # Required for certain MPI versions (since gfortran version 10) + gfortran.add_flags("-fallow-argument-mismatch", "base") + + runtime = ["-fcheck=all", "-ffpe-trap=invalid,zero,overflow"] + init = ["-finit-integer=31173", "-finit-real=snan", + "-finit-logical=true", "-finit-character=85"] + # Full debug + # ========== + gfortran.add_flags(runtime + ["-O0"] + init, "full-debug") + + # Fast debug + # ========== + gfortran.add_flags(runtime + ["-Og"], "fast-debug") + + # Production + # ========== + gfortran.add_flags(["-Ofast"], "production") + + # unit-tests + # ========== + gfortran.add_flags(runtime + ["-O0"] + init, "unit-tests") + + # Set up the linker + # ================= + # This will implicitly affect all gfortran based linkers, e.g. + # linker-mpif90-gfortran will use these flags as well. + linker = tr.get_tool(Category.LINKER, "linker-gfortran") + linker = cast(Linker, linker) + + # ATM we don't use a shell when running a tool, and as such + # we can't directly use "$()" as parameter. So query these values using + # Fab's shell tool (doesn't really matter which shell we get, so just + # ask for the default): + shell = tr.get_default(Category.SHELL) + + try: + # We must remove the trailing new line, and create a list: + nc_flibs = shell.run(additional_parameters=["-c", "nf-config --flibs"], + capture_output=True).strip().split() + except RuntimeError: + nc_flibs = [] + + linker.add_lib_flags("netcdf", nc_flibs) + linker.add_lib_flags("hdf5", ["-lhdf5"]) diff --git a/fab/site_specific/default/setup_script_intel_classic.py b/fab/site_specific/default/setup_script_intel_classic.py new file mode 100644 index 00000000..638e95cb --- /dev/null +++ b/fab/site_specific/default/setup_script_intel_classic.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +'''This file contains a function that sets the default flags for all +Intel classic based compilers in the ToolRepository (ifort, icc). + +This function gets called from the default site-specific config file +''' + +import argparse +from typing import cast + +from fab.api import BuildConfig, Category, Compiler, Linker, ToolRepository + + +def setup_script_intel_classic(build_config: BuildConfig, + args: argparse.Namespace): + # pylint: disable=unused-argument, too-many-locals + '''Defines the default flags for all Intel classic compilers. + + :para build_config: the build config from which required parameters + can be taken. + :param args: all command line options + ''' + + tr = ToolRepository() + ifort = tr.get_tool(Category.FORTRAN_COMPILER, "ifort") + ifort = cast(Compiler, ifort) + + if not ifort.is_available: + # This can happen if ifort is not in path (in spack environments). + # To support this common use case, see if mpif90-ifort is available, + # and initialise this otherwise. + ifort = tr.get_tool(Category.FORTRAN_COMPILER, "mpif90-ifort") + ifort = cast(Compiler, ifort) + if not ifort.is_available: + # Since some flags depends on version, the code below requires + # that the intel compiler actually works. + return + + # The base flags + # ============== + # The following flags will be applied to all modes: + ifort.add_flags(["-stand", "f08"], "base") + ifort.add_flags(["-g", "-traceback"], "base") + # With -warn errors we get externals that are too long. While this + # is a (usually safe) warning, the long externals then causes the + # build to abort. So for now we cannot use `-warn errors` + ifort.add_flags(["-warn", "all"], "base") + + # By default turning interface warnings on causes "genmod" files to be + # created. This adds unnecessary files to the build so we disable that + # behaviour. + ifort.add_flags(["-gen-interfaces", "nosource"], "base") + + # The "-assume realloc-lhs" switch causes Intel Fortran prior to v17 to + # actually implement the Fortran2003 standard. At version 17 it becomes the + # default behaviour. + if ifort.get_version() < (17, 0): + ifort.add_flags(["-assume", "realloc-lhs"], "base") + + # Full debug + # ========== + # ifort.mk: bad interaction between array shape checking and + # the matmul" intrinsic in at least some iterations of v19. + if (19, 0, 0) <= ifort.get_version() < (19, 1, 0): + runtime_flags = ["-check", "all,noshape", "-fpe0"] + else: + runtime_flags = ["-check", "all", "-fpe0"] + ifort.add_flags(runtime_flags, "full-debug") + ifort.add_flags(["-O0", "-ftrapuv"], "full-debug") + + # Fast debug + # ========== + ifort.add_flags(["-O2", "-fp-model=strict"], "fast-debug") + + # Production + # ========== + ifort.add_flags(["-O3", "-xhost"], "production") + + # Set up the linker + # ================= + # This will implicitly affect all ifort based linkers, e.g. + # linker-mpif90-ifort will use these flags as well. + linker = tr.get_tool(Category.LINKER, "linker-ifort") + linker = cast(Linker, linker) + + # ATM we don't use a shell when running a tool, and as such + # we can't directly use "$()" as parameter. So query these values using + # Fab's shell tool (doesn't really matter which shell we get, so just + # ask for the default): + shell = tr.get_default(Category.SHELL) + try: + # We must remove the trailing new line, and create a list: + nc_flibs = shell.run(additional_parameters=["-c", "nf-config --flibs"], + capture_output=True).strip().split() + except RuntimeError: + nc_flibs = [] + + linker.add_lib_flags("netcdf", nc_flibs) + linker.add_lib_flags("yaxt", ["-lyaxt", "-lyaxt_c"]) + linker.add_lib_flags("xios", ["-lxios"]) + linker.add_lib_flags("hdf5", ["-lhdf5"]) + + # Always link with C++ libs + linker.add_post_lib_flags(["-lstdc++"]) diff --git a/fab/site_specific/default/setup_script_intel_llvm.py b/fab/site_specific/default/setup_script_intel_llvm.py new file mode 100644 index 00000000..a81b0474 --- /dev/null +++ b/fab/site_specific/default/setup_script_intel_llvm.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +'''This file contains a function that sets the default flags for all +Intel llvm based compilers in the ToolRepository (ifx, icx). + +This function gets called from the default site-specific config file +''' + +import argparse +from typing import cast + +from fab.api import BuildConfig, Category, Compiler, Linker, ToolRepository + + +def setup_script_intel_llvm(build_config: BuildConfig, + args: argparse.Namespace): + # pylint: disable=unused-argument, too-many-locals + '''Defines the default flags for all Intel llvm compilers. + + :para build_config: the build config from which required parameters + can be taken. + :param args: all command line options + ''' + + tr = ToolRepository() + ifx = tr.get_tool(Category.FORTRAN_COMPILER, "ifx") + ifx = cast(Compiler, ifx) + + if not ifx.is_available: + ifx = tr.get_tool(Category.FORTRAN_COMPILER, "mpif90-ifx") + ifx = cast(Compiler, ifx) + if not ifx.is_available: + return + + # The base flags + # ============== + # The following flags will be applied to all modes: + ifx.add_flags(["-stand", "f08"], "base") + ifx.add_flags(["-g", "-traceback"], "base") + # With -warn errors we get externals that are too long. While this + # is a (usually safe) warning, the long externals then causes the + # build to abort. So for now we cannot use `-warn errors` + ifx.add_flags(["-warn", "all"], "base") + + # By default turning interface warnings on causes "genmod" files to be + # created. This adds unnecessary files to the build so we disable that + # behaviour. + ifx.add_flags(["-gen-interfaces", "nosource"], "base") + + # Full debug + # ========== + ifx.add_flags(["-check", "all", "-fpe0"], "full-debug") + ifx.add_flags(["-O0", "-ftrapuv"], "full-debug") + + # Fast debug + # ========== + ifx.add_flags(["-O2", "-fp-model=strict"], "fast-debug") + + # Production + # ========== + ifx.add_flags(["-O3", "-xhost"], "production") + + # Set up the linker + # ================= + # This will implicitly affect all ifx based linkers, e.g. + # linker-mpif90-ifx will use these flags as well. + linker = tr.get_tool(Category.LINKER, "linker-ifx") + linker = cast(Linker, linker) # Make mypy happy + # ATM we don't use a shell when running a tool, and as such + # we can't directly use "$()" as parameter. So query these values using + # Fab's shell tool (doesn't really matter which shell we get, so just + # ask for the default): + shell = tr.get_default(Category.SHELL) + try: + # We must remove the trailing new line, and create a list: + nc_flibs = shell.run(additional_parameters=["-c", "nf-config --flibs"], + capture_output=True).strip().split() + except RuntimeError: + nc_flibs = [] + + linker.add_lib_flags("netcdf", nc_flibs) + linker.add_lib_flags("yaxt", ["-lyaxt", "-lyaxt_c"]) + linker.add_lib_flags("xios", ["-lxios"]) + linker.add_lib_flags("hdf5", ["-lhdf5"]) + + # Always link with C++ libs + linker.add_post_lib_flags(["-lstdc++"]) diff --git a/fab/site_specific/default/setup_script_nvidia.py b/fab/site_specific/default/setup_script_nvidia.py new file mode 100644 index 00000000..f583d4ad --- /dev/null +++ b/fab/site_specific/default/setup_script_nvidia.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +'''This file contains a function that sets the default flags for the NVIDIA +compilers in the ToolRepository. + +This function gets called from the default site-specific config file +''' + +import argparse +from typing import cast + +from fab.api import BuildConfig, Category, Compiler, Linker, ToolRepository + + +def setup_script_nvidia(build_config: BuildConfig, args: argparse.Namespace): + # pylint: disable=unused-argument + '''Defines the default flags for nvfortran. + + :param build_config: the build config from which required parameters + can be taken. + :param args: all command line options + ''' + + tr = ToolRepository() + nvfortran = tr.get_tool(Category.FORTRAN_COMPILER, "nvfortran") + nvfortran = cast(Compiler, nvfortran) + + if not nvfortran.is_available: + nvfortran = tr.get_tool(Category.FORTRAN_COMPILER, "mpif90-nvfortran") + nvfortran = cast(Compiler, nvfortran) + if not nvfortran.is_available: + return + + # The base flags + # ============== + flags = ["-Mextend", # 132 characters line length + "-g", "-traceback", + "-r8", # Default 8 bytes reals + "-O0", # No optimisations + ] + + lib_flags = ["-c++libs"] + + # Handle accelerator options: + if args.openacc or args.openmp: + host = args.host.lower() + else: + # Neither openacc nor openmp specified + host = "" + + if args.openacc: + if host == "gpu": + flags.extend(["-acc=gpu", "-gpu=managed"]) + lib_flags.extend(["-aclibs", "-cuda"]) + else: + # CPU + flags.extend(["-acc=cpu"]) + elif args.openmp: + if host == "gpu": + flags.extend(["-mp=gpu", "-gpu=managed"]) + lib_flags.append("-cuda") + else: + # OpenMP on CPU, that's already handled by Fab + pass + + nvfortran.add_flags(flags, "base") + + # Full debug + # ========== + nvfortran.add_flags(["-O0", "-fp-model=strict"], "full-debug") + + # Fast debug + # ========== + nvfortran.add_flags(["-O2", "-fp-model=strict"], "fast-debug") + + # Production + # ========== + nvfortran.add_flags(["-O4"], "production") + + # Set up the linker + # ================= + # This will implicitly affect all nvfortran based linkers, e.g. + # linker-mpif90-nvfortran will use these flags as well. + linker = tr.get_tool(Category.LINKER, "linker-nvfortran") + linker = cast(Linker, linker) + + # ATM we don't use a shell when running a tool, and as such + # we can't directly use "$()" as parameter. So query these values using + # Fab's shell tool (doesn't really matter which shell we get, so just + # ask for the default): + shell = tr.get_default(Category.SHELL) + try: + # We must remove the trailing new line, and create a list: + nc_flibs = shell.run(additional_parameters=["-c", "nf-config --flibs"], + capture_output=True).strip().split() + except RuntimeError: + nc_flibs = [] + + linker.add_lib_flags("netcdf", nc_flibs) + linker.add_lib_flags("yaxt", ["-lyaxt", "-lyaxt_c"]) + linker.add_lib_flags("xios", ["-lxios"]) + linker.add_lib_flags("hdf5", ["-lhdf5"]) + + # Always link with C++ libs + linker.add_post_lib_flags(lib_flags)