From 670313ef0818d70bf9445c89a83a465b835dba1b Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 30 Jul 2026 17:17:25 +1000 Subject: [PATCH 1/5] #5 Add first basic Fab script for building MONC with gnu compiler. --- fab/.gitignore | 1 + fab/README.md | 85 ++++++++ fab/fab_monc.py | 169 +++++++++++++++ fab/nf_config.py | 37 ++++ fab/site_specific/default/config.py | 200 ++++++++++++++++++ fab/site_specific/default/setup_script_gnu.py | 87 ++++++++ 6 files changed, 579 insertions(+) create mode 100644 fab/.gitignore create mode 100644 fab/README.md create mode 100755 fab/fab_monc.py create mode 100644 fab/nf_config.py create mode 100644 fab/site_specific/default/config.py create mode 100644 fab/site_specific/default/setup_script_gnu.py diff --git a/fab/.gitignore b/fab/.gitignore new file mode 100644 index 0000000..1411427 --- /dev/null +++ b/fab/.gitignore @@ -0,0 +1 @@ +fab-workspace diff --git a/fab/README.md b/fab/README.md new file mode 100644 index 0000000..95011f4 --- /dev/null +++ b/fab/README.md @@ -0,0 +1,85 @@ +# Fab Build Scripts for MONC + +This directory contains the files for building MONC with Fab. + +You need Fab version 2.2.0 (or later). + +## Building +The build script is a Python script that relies on Fab. +MONC can be compiled by providing the required +command line options to the script. + +Please read the Fab documentation (esp the +[introduction to the Fab base class](https://metoffice.github.io/fab/fab_base/index.html) +for details. See also the next section ('setup') if you need site-specific +modifications. + +The actual build is defined by command line parameters to the +``fab_monc.py`` script. Use the ``-h`` option to list all available +options. Many options are inherited from the Fab base class, only +MONC specific options are defined in ``fab_monc.py`` and are grouped +at the end of the help output:: + + MONC configuration options: + --petsc Enable the usage of PETSc. (default: False) + --casim Enable the usage of CASIM. (default: False) + --casim-profile-dgs Enable the usage of CASIM profile dgs. (default: False) + --socrates Enable the usage of SOCRATES. (default: False) + +In the ``site_specific`` directory are various site-specific +setups. The main one is called ``default``, and it contains setting +for any compiler currently supported by Fab. But each site can +modify the settings. Have a look at the existing configurations +already contained in MONC, and check the +[Fab documentation](https://metoffice.github.io/fab/fab_base/config.html) +for a full explanation of the available options. + +Specific for MONC, I have added a `NfConfig.py` class, which wraps +the detection of NetCDF using the `nf-config` tools. This is a copy +of the file used in LFRic_core. A site can overwrite the settings +if e.g. `nf-config` is not available or not working properly. + +An example build can be done as follows:: + + ./fab_monc.py --site nci --platform gadi --suite gnu --profile debug + +The compilers are selected by specifying a suite (Fab supports out of +the box ``gnu``, ``intel-classic``, ``intel-llvm``, ``nvidia`` and +``cray``), and it will use the corresponding Fortran and C compiler. +If MPI is enabled (which is the default), Fab will search for +corresponding ``mpif90`` and ``mpicc`` compiler wrapper, and verify +that they are indeed of the right suite. If you need to use say +a different C compiler (e.g. use ``gcc`` in an otherwise Intel build), +use the ``-cc`` command line option. + +Any Fab script also supports the ``--available-compilers`` flag, which +just lists all compilers that Fab knows about that are available on the +system. Example output (of a system that has ``gfortran`` and ``mpif90`` +as a wrapper for gfortran):: + + ----- Available compiler and linkers ----- + Gcc - gcc: gcc + Mpicc - mpicc-gcc: mpicc + Gfortran - gfortran: gfortran + Mpif90 - mpif90-gfortran: mpif90 + Linker - linker-gcc: gcc + Linker - linker-gfortran: gfortran + Linker - linker-mpif90-gfortran: mpif90 + Linker - linker-mpicc-gcc: mpicc + +The Fab workspace defaults to ``./fab-workspace``, but this can be +changed using the ``--fab-workspace`` command line option. + +If the build finished successfully, the binary will be in the +directory ``fab-workspace/monc-debug-gfortran/``, it is +called ``monc_driver``. The actual directory name will depend on the +options specified of course. + +## Setting up site-specific options +If you need site-specific options (e.g. you want to change the +default compiler flags used for your compiler), create +a directory with the name of your site and platform under +``site-specific``. Please check the existing +[Fab documentation](https://metoffice.github.io/fab/fab_base/config.html) +for examples on setting up options, or the existing +site-specific setups under ``fab/site-specific``. diff --git a/fab/fab_monc.py b/fab/fab_monc.py new file mode 100755 index 0000000..485ce09 --- /dev/null +++ b/fab/fab_monc.py @@ -0,0 +1,169 @@ +#!/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 Fab-based build script for the Unified Model. +''' + +import argparse +import logging +from pathlib import Path +from typing import cast, Iterable, List, Optional, Union + +from fab.fab_base.fab_base import FabBase +from fab.api import (AddFlags, Category, Compiler, Exclude, find_source_files, + grab_folder, Include, root_inc_files) + +# Since we don't have a proper python package, we cannot use __name__, so set +# up an appropriate dotted name for logging: +logger = logging.getLogger("monc.fab.fab_monc") + + +class FabMonc(FabBase): + ''' + A class to build MONC using Fab as base class. + + :param str name: name of the build. + ''' + + def __init__(self, name): + super().__init__(name) + # We need to overwrite the name of the main program, it + # defaults to `monc`. + self.set_root_symbols("monc_driver") + # Store the root directory of MONC: + self._root = Path(__file__).resolve().parents[1] + + def define_command_line_options( + self, + parser: Optional[argparse.ArgumentParser] = None + ) -> argparse.ArgumentParser: + ''' + Adds the additionally required command line options for the UM. + + :param Optional[argparse.ArgumentParser] parser: a pre-defined + argument parser. If not, a new instance will be created. + + :returns: the argument parser with the UM specific options added. + ''' + + parser = super().define_command_line_options(parser) + parser = cast(argparse.ArgumentParser, parser) + + monc_config = parser.add_argument_group("MONC configuration options") + monc_config.add_argument( + "--petsc", action="store_true", default=False, + help="Enable the usage of PETSc.") + monc_config.add_argument( + "--casim", action="store_true", default=False, + help="Enable the usage of CASIM.") + monc_config.add_argument( + "--casim-profile-dgs", action="store_true", + default=False, help="Enable the usage of CASIM profile dgs.") + monc_config.add_argument( + "--socrates", action="store_true", default=False, + help="Enable the usage of SOCRATES.") + + return parser + + def grab_files_step(self) -> None: + ''' + Extracts all the required source files from the repositories. + It then sets the include path (since include files are not + copied into the build tree). + + :raises RuntimeError: the expected `rose-meta/um-atmos` file + does not exist, indicating an invalid directory structure. + ''' + for directory in ["components", "io", "misc", "model_core", + "testcases"]: + grab_folder(self.config, self._root / directory, + dst_label=directory) + + def find_source_files_step( + self, + path_filters: Optional[Iterable[Union[Exclude, Include]]] = None + ): + ''' + Finds all the UM sources files to analyse. If the tests are + being compiled, only search the tests directory. + ''' + path_filters = [Exclude("model_core/test")] + if self.args.petsc: + # Exclude the stub if we are using PETSc + path_filters.append(Exclude("petsc_solver_stub.F90")) + else: + # No PETSc, ignore the solver (and use the stub) + path_filters.append(Exclude("petsc_solver.F90")) + + if self.args.casim: + path_filters.append(Exclude("casim_stub.F90")) + else: + path_filters.append(Exclude("casim.F90")) + + if self.args.casim_profile_dgs: + path_filters.append(Exclude("casim_profile_dgs_stub.F90")) + else: + path_filters.append(Exclude("casim_profile_dgs.F90")) + + if self.args.socrates: + path_filters.append(Exclude("socrates_couple_stub.F90")) + else: + path_filters.append(Exclude("socrates_couple.F90")) + + find_source_files(self.config, + path_filters=path_filters) + root_inc_files(self.config, [".h", ".static"]) + + def define_preprocessor_flags_step(self) -> None: + ''' + Defines the preprocessor flags. + ''' + super().define_preprocessor_flags_step() + + flags = ['-DU_ACTIVE', '-DV_ACTIVE', '-DW_ACTIVE', + '-DENFORCE_THREAD_SAFETY', '-D__DARWIN', + '-D_XOPEN_SOURCE=700', + '-I$output', + ] + if self.args.profile == "debug": + flags.append("-DDEBUG_MODE") + + self.add_preprocessor_flags(flags) + + def compile_fortran_step( + self, + common_flags: Optional[List[str]] = None, + path_flags: Optional[List[AddFlags]] = None + ) -> None: + + fc = self.config.tool_box.get_tool(Category.FORTRAN_COMPILER) + fc = cast(Compiler, fc) + new_flags = [] + if common_flags: + new_flags.extend(common_flags) + + super().compile_fortran_step(common_flags=new_flags, + path_flags=path_flags) + + def get_linker_flags(self) -> List[str]: + return ["netcdf"] + + +# ========================================================================== +if __name__ == "__main__": + + # Initialise a top-level logger + logger = logging.getLogger('um') + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + formatter = logging.Formatter('%(levelname)s: %(name)s: %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + fab_monc = FabMonc("monc") + fab_monc.build() diff --git a/fab/nf_config.py b/fab/nf_config.py new file mode 100644 index 0000000..4d09c30 --- /dev/null +++ b/fab/nf_config.py @@ -0,0 +1,37 @@ +############################################################################## +# (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 file contains the class to interface with NetCDF's nf-config script. +""" + +from typing import List + +from fab.tools.category import Category +from fab.tools.tool import Tool + + +class NfConfig(Tool): + '''This class interfaces with NetCDF's nf-config tool. It is not added + to the ToolRepository, it is intended for site-specific configurations + to make it easier to query for NetCDF settings. + ''' + + def __init__(self): + super().__init__("nf-config", "nf-config", Category.MISC) + + def get_compiler_flags(self) -> List[str]: + """ + :returns: the compilation flags to use for NetCDF. + """ + flags = self.run(additional_parameters=["--fflags"]) + return flags.split() + + def get_linker_flags(self) -> List[str]: + """ + :returns: the linker flags to use for NetCDF. + """ + flags = self.run(additional_parameters=["--flibs"]) + return flags.split() diff --git a/fab/site_specific/default/config.py b/fab/site_specific/default/config.py new file mode 100644 index 0000000..3b1a14a --- /dev/null +++ b/fab/site_specific/default/config.py @@ -0,0 +1,200 @@ +#! /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 the default Baf configuration class. +''' + +import argparse +from typing import List + +from fab.api import AddFlags, 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) -> None: + self._args: argparse.Namespace + + @property + def args(self) -> argparse.Namespace: + ''' + :returns: 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 of all supported compiler profiles. + ''' + return ["debug", "safe", "high"] + + def define_command_line_options(self, + parser: argparse.ArgumentParser) -> None: + ''' + Callback in which additional, site-specific options can be added, + and/or the the defaults for the parser can be changed. + ''' + # As examples (typically used in a derived, site-specific class): + # Adding a site-specific option to profile with Tau: + # parser.add_argument("--tau", default=False, action="store_true", + # help="Enable tau profiling") + + # Second example: change the default for an existing option, e.g. + # disabling MPI by default: + # parser.set_defaults(mpi=False) + + 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 update_repos(self, dep_info): + """ + This method is called by the main script to allow each site to + replace the URLs of repos with e.g. local mirrors. + """ + + # A simplified example to use mirrors could be (which would + # typically be implemented in a derived, site-specific class) + # root = Path("/root/of/mirrors") + # mirrors = {"git@github.com:MetOffice/casim.git": root / "casim", + # "git@github.com:MetOffice/jules.git": root / "jules", + # } + # for dependency in dep_info.get_repo_names(): + # repo_infos = dep_info.get_repo_info(dependency) + # for source_ref in repo_infos: + # if source_ref.source in mirrors: + # logger.info(f"Using mirror " + # f"'{mirrors[source_ref.source]}' for " + # f"'{source_ref.source}") + # source_ref.source = mirrors[source_ref.source] + + 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 + ''' + # 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]): + # 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. + # Note that we set this even if a compiler is not available. + # This is required in case that compilers are not in PATH, + # so e.g. mpif90-ifort works, but ifort cannot be found. + # We still need to be able to set and query flags for ifort. + 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 get_path_flags(self, build_config: BuildConfig) -> List[AddFlags]: + ''' + Returns the path-specific flags to be used. + TODO #313: Ideally we have only one kind of flag, but as a quick + work around we provide this method. + + :param build_config: the Fab build configuration instance + ''' + return [] + + 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 + ''' + 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 + ''' + 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 + ''' + 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 + ''' + 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 + ''' + setup_script_nvidia(build_config, self.args) 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 0000000..ee08b06 --- /dev/null +++ b/fab/site_specific/default/setup_script_gnu.py @@ -0,0 +1,87 @@ +#!/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 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, Compiler, ContainFlags, Linker, + ToolRepository) + +from nf_config import NfConfig + + +def setup_script_gnu(build_config: BuildConfig, + args: argparse.Namespace) -> None: + # 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 + gfortran = cast(Compiler, gfortran) + + gcc = tr.get_tool(Category.C_COMPILER, "gcc") + if not gcc.is_available: + gcc = tr.get_tool(Category.C_COMPILER, "mpif90-gcc") + if not gcc.is_available: + return + gcc = cast(Compiler, gcc) + + # The base flags + # ============== + default_flags = ['-frecursive', '-g', '-fallow-argument-mismatch'] + + gfortran.add_flags(default_flags, 'base') + + + gcc.add_flags(["-fcommon"], "safe") + gcc.add_flags(["-fcommon"], "debug") + + # Debug + # ===== + gfortran.add_flags(['-O0', '-Wall', '-fcheck=all', + '-ffpe-trap=zero,invalid,overflow', + '-fallow-invalid-boz'], "debug") + + # Safe + # ==== + gfortran.add_flags(['-O2', '-fbounds-check', '-fallow-invalid-boz', + '-fallow-invalid-boz'], "safe") + + # High + # ==== + gfortran.add_flags(['-O3', '-pg'], "high") + + + # 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, f"linker-{gfortran.name}") + linker = cast(Linker, linker) + + # This likely needs to be update for each site (e.g. adding paths) + nf_config = NfConfig() + if nf_config.is_available: + # If not available, the site-specific setup must define netcdf + linker.add_lib_flags("netcdf", nf_config.get_linker_flags()) From c22d8f19c906e87e444a977ffb3596f98103493c Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 30 Jul 2026 17:17:52 +1000 Subject: [PATCH 2/5] #5 For now duplicate monc_driver, since Fab cannot copy a single file. --- model_core/monc_driver.F90 | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 model_core/monc_driver.F90 diff --git a/model_core/monc_driver.F90 b/model_core/monc_driver.F90 new file mode 100644 index 0000000..115b261 --- /dev/null +++ b/model_core/monc_driver.F90 @@ -0,0 +1,46 @@ +!> MONC program entry point which simply calls the main procedure in the MONC module +! +program monc_driver + use io_server_mod, only : io_server_run + use monc_mod, only : monc_core_bootstrap + use monc_component_mod, only : component_descriptor_type + use collections_mod, only : list_type, c_add_generic + ! Include the autogenerated use modules for each of the components +#ifdef USE_MAKE +#include "components/componentheaders.autogen" +#include "testcases/testcaseheaders.autogen" +#else +#include "componentheaders.static" +#include "testcaseheaders.static" +#endif + implicit none + + type(list_type) :: component_descriptions + + call get_compiled_components(component_descriptions) + call monc_core_bootstrap(component_descriptions, io_server_run) +contains + + subroutine get_compiled_components(component_descriptions) + type(list_type), intent(inout) :: component_descriptions + + ! Include the autogenerated registration calls for each of the components +#ifdef USE_MAKE +#include "components/componentregistrations.autogen" +#include "testcases/testcaseregistrations.autogen" +#else +#include "componentregistrations.static" +#include "testcaseregistrations.static" +#endif + end subroutine get_compiled_components + + !> Called by each component to add itself to the registration list_type + subroutine add_component(component_descriptions, single_description) + type(list_type), intent(inout) :: component_descriptions + type(component_descriptor_type), intent(in) :: single_description + + class(*), pointer :: raw_data + allocate(raw_data, source=single_description) + call c_add_generic(component_descriptions, raw_data, .false.) + end subroutine add_component +end program monc_driver From 0a04aa14374e9426a6afe7589baadff94240fa35 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 30 Jul 2026 17:19:44 +1000 Subject: [PATCH 3/5] #5 Updated contributor. --- CONTRIBUTORS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index caa46c9..af4cc45 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,7 +1,8 @@ # Contributors -| GitHub user | Real Name | Affiliation | Date | -| --------------- | --------------- | --------------------- | ---------- | -| james-bruten-mo | James Bruten | Met Office | 2025-12-09 | -| trjr | Todd Jones | University of Reading | 2026-05-01 | -| yaswant | Yaswant Pradhan | Met Office | 2026-07-17 | +| GitHub user | Real Name | Affiliation | Date | +| --------------- | --------------- | -------------------------------- | ---------- | +| james-bruten-mo | James Bruten | Met Office | 2025-12-09 | +| trjr | Todd Jones | University of Reading | 2026-05-01 | +| yaswant | Yaswant Pradhan | Met Office | 2026-07-17 | +| hiker | Joerg Henrichs | Bureau of Meteorology, Australia | 2026-07-30 | From 5d04dc8c3b6c66a30871984146936728413fb711 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 30 Jul 2026 23:40:01 +1000 Subject: [PATCH 4/5] #5 Start to add file-specific gfortran flags. --- fab/site_specific/default/setup_script_gnu.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/fab/site_specific/default/setup_script_gnu.py b/fab/site_specific/default/setup_script_gnu.py index ee08b06..f3ca667 100644 --- a/fab/site_specific/default/setup_script_gnu.py +++ b/fab/site_specific/default/setup_script_gnu.py @@ -49,29 +49,42 @@ def setup_script_gnu(build_config: BuildConfig, # The base flags # ============== - default_flags = ['-frecursive', '-g', '-fallow-argument-mismatch'] + default_flags = ['-frecursive', '-g', + '-fallow-argument-mismatch' + ] gfortran.add_flags(default_flags, 'base') - - gcc.add_flags(["-fcommon"], "safe") - gcc.add_flags(["-fcommon"], "debug") - # Debug # ===== gfortran.add_flags(['-O0', '-Wall', '-fcheck=all', '-ffpe-trap=zero,invalid,overflow', '-fallow-invalid-boz'], "debug") + gcc.add_flags(["-fcommon"], "debug") + + # ContainFlags uses a substring test. So add / and . + # to make sure we match the full filename + psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", + "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", + "/iterativesolver_single_prec.f90"] # Safe # ==== gfortran.add_flags(['-O2', '-fbounds-check', '-fallow-invalid-boz', '-fallow-invalid-boz'], "safe") + gcc.add_flags(["-fcommon"], "safe") + + for fname in psrc: + gfortran.add_flags(ContainFlags(fname, + ["-O1", + "-ffpe-trap=zero,invalid,overflow"]), + "safe") # High # ==== gfortran.add_flags(['-O3', '-pg'], "high") - + for fname in psrc: + gfortran.add_flags(ContainFlags(fname, ["-O1"]), "debug") # Set up the linker # ================= From 46fe07ad0688a8f62a8d5f4933bca737b183bbbd Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 31 Jul 2026 13:57:14 +1000 Subject: [PATCH 5/5] #5 Added settings for all other supported compilers. --- fab/site_specific/default/config.py | 31 ----- .../default/setup_script_cray.py | 109 ++++++++++++++++++ fab/site_specific/default/setup_script_gnu.py | 34 +++--- .../default/setup_script_intel_classic.py | 97 ++++++++++++++++ .../default/setup_script_intel_llvm.py | 98 ++++++++++++++++ .../default/setup_script_nvidia.py | 97 ++++++++++++++++ 6 files changed, 418 insertions(+), 48 deletions(-) create mode 100644 fab/site_specific/default/setup_script_cray.py create mode 100644 fab/site_specific/default/setup_script_intel_classic.py create mode 100644 fab/site_specific/default/setup_script_intel_llvm.py create mode 100644 fab/site_specific/default/setup_script_nvidia.py diff --git a/fab/site_specific/default/config.py b/fab/site_specific/default/config.py index 3b1a14a..c9c3e4c 100644 --- a/fab/site_specific/default/config.py +++ b/fab/site_specific/default/config.py @@ -78,27 +78,6 @@ def handle_command_line_options(self, args: argparse.Namespace) -> None: # initialising compilers self._args = args - def update_repos(self, dep_info): - """ - This method is called by the main script to allow each site to - replace the URLs of repos with e.g. local mirrors. - """ - - # A simplified example to use mirrors could be (which would - # typically be implemented in a derived, site-specific class) - # root = Path("/root/of/mirrors") - # mirrors = {"git@github.com:MetOffice/casim.git": root / "casim", - # "git@github.com:MetOffice/jules.git": root / "jules", - # } - # for dependency in dep_info.get_repo_names(): - # repo_infos = dep_info.get_repo_info(dependency) - # for source_ref in repo_infos: - # if source_ref.source in mirrors: - # logger.info(f"Using mirror " - # f"'{mirrors[source_ref.source]}' for " - # f"'{source_ref.source}") - # source_ref.source = mirrors[source_ref.source] - def update_toolbox(self, build_config: BuildConfig) -> None: ''' Set the default compiler flags for the various compiler @@ -134,16 +113,6 @@ def update_toolbox(self, build_config: BuildConfig) -> None: self.setup_nvidia(build_config) self.setup_cray(build_config) - def get_path_flags(self, build_config: BuildConfig) -> List[AddFlags]: - ''' - Returns the path-specific flags to be used. - TODO #313: Ideally we have only one kind of flag, but as a quick - work around we provide this method. - - :param build_config: the Fab build configuration instance - ''' - return [] - def setup_cray(self, build_config: BuildConfig) -> None: ''' This method sets up the Cray compiler and linker flags. 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 0000000..fd493ec --- /dev/null +++ b/fab/site_specific/default/setup_script_cray.py @@ -0,0 +1,109 @@ +#!/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 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, ContainFlags, Linker, + ToolRepository) + +from nf_config import NfConfig + + +def setup_script_cray(build_config: BuildConfig, + args: argparse.Namespace) -> None: + # 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 = ["-e", "m"] + + # 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([]) + + ftn.add_flags(flags, "base") + ftn.add_flags(ContainFlags("/model_core/", ["-e", "R"]), "base") + ftn.add_flags(ContainFlags("/io/", ["-e", "R"]), "base") + + # The following files have special flags in some modes. ContainFlags + # uses a substring test. Add '/' to make sure we match the full filename. + psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", + "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", + "/iterativesolver_single_prec.f90"] + + # Debug + # ===== + ftn.add_flags(["-g", + "-Ktrap=divz,inv,ovf", # floating point checking + "-R", "bcdps", # bounds, array shape, collapse, + # pointer, string checking + "-O0"], # No optimisation + "debug") + + # Safe + # ==== + ftn.add_flags(["-O2", "-Ovector1", "-hfp0", "-hflex_mp=strict"], "safe") + # Special flags: + for pattern in psrc: + ftn.add_flags(ContainFlags(pattern, ["-g", "-Ktrap=divz,inv,ovf", + "-R", "bcdps", "-O0"]), "safe") + + # High + # ==== + ftn.add_flags(["-O3"], "high") + # Special flags: + for pattern in psrc: + ftn.add_flags(ContainFlags(pattern, ["-O1"]), "high") + + # Set up the linker + # ================= + linker = tr.get_tool(Category.LINKER, f"linker-{ftn.name}") + linker = cast(Linker, linker) + + # As default, use nf-config to set NetCDF linker flags. If it's not + # available (or not working properly), the site-specific setup must + # add netcdf definitions. + nf_config = NfConfig() + if nf_config.is_available: + linker.add_lib_flags("netcdf", nf_config.get_linker_flags()) diff --git a/fab/site_specific/default/setup_script_gnu.py b/fab/site_specific/default/setup_script_gnu.py index f3ca667..608f9a5 100644 --- a/fab/site_specific/default/setup_script_gnu.py +++ b/fab/site_specific/default/setup_script_gnu.py @@ -49,11 +49,18 @@ def setup_script_gnu(build_config: BuildConfig, # The base flags # ============== - default_flags = ['-frecursive', '-g', - '-fallow-argument-mismatch' - ] - + default_flags = ['-g'] gfortran.add_flags(default_flags, 'base') + if gfortran.get_version() >= (10, 0): + gfortran.add_flags("-fallow-argument-mismatch", "base") + gfortran.add_flags(ContainFlags("/model_core/", "-frecursive"), "base") + gfortran.add_flags(ContainFlags("/io/", "-frecursive"), "base") + + # The following files have special flags in some modes. ContainFlags + # uses a substring test. Add '/' to make sure we match the full filename. + psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", + "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", + "/iterativesolver_single_prec.f90"] # Debug # ===== @@ -62,20 +69,14 @@ def setup_script_gnu(build_config: BuildConfig, '-fallow-invalid-boz'], "debug") gcc.add_flags(["-fcommon"], "debug") - # ContainFlags uses a substring test. So add / and . - # to make sure we match the full filename - psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", - "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", - "/iterativesolver_single_prec.f90"] - # Safe # ==== gfortran.add_flags(['-O2', '-fbounds-check', '-fallow-invalid-boz', '-fallow-invalid-boz'], "safe") gcc.add_flags(["-fcommon"], "safe") - for fname in psrc: - gfortran.add_flags(ContainFlags(fname, + for pattern in psrc: + gfortran.add_flags(ContainFlags(pattern, ["-O1", "-ffpe-trap=zero,invalid,overflow"]), "safe") @@ -84,17 +85,16 @@ def setup_script_gnu(build_config: BuildConfig, # ==== gfortran.add_flags(['-O3', '-pg'], "high") for fname in psrc: - gfortran.add_flags(ContainFlags(fname, ["-O1"]), "debug") + gfortran.add_flags(ContainFlags(fname, ["-O1", "-pg"]), "debug") # 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, f"linker-{gfortran.name}") linker = cast(Linker, linker) - # This likely needs to be update for each site (e.g. adding paths) + # As default, use nf-config to set NetCDF linker flags. If it's not + # available (or not working properly), the site-specific setup must + # add netcdf definitions. nf_config = NfConfig() if nf_config.is_available: - # If not available, the site-specific setup must define netcdf linker.add_lib_flags("netcdf", nf_config.get_linker_flags()) 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 0000000..8bea7d9 --- /dev/null +++ b/fab/site_specific/default/setup_script_intel_classic.py @@ -0,0 +1,97 @@ +#!/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 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, ContainFlags, Linker, + ToolRepository) + +from nf_config import NfConfig + + +def setup_script_intel_classic(build_config: BuildConfig, + args: argparse.Namespace) -> None: + # 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 + + icc = tr.get_tool(Category.C_COMPILER, "icc") + icc = cast(Compiler, icc) + if not icc.is_available: + icc = tr.get_tool(Category.C_COMPILER, "mpicc-icc") + icc = cast(Compiler, icc) + + # The base flags + # ============== + # The following flags will be applied to all modes: + ifort.add_flags(["-g", "-traceback", "-fp-model", "precise"], "base") + ifort.add_flags(ContainFlags("/model_core/", "-recursive"), "base") + ifort.add_flags(ContainFlags("/io/", "-recursive"), "base") + + icc.add_flags(["-g", "-traceback", "-std=gnu99"], "base") + + # The following files have special flags in some modes. ContainFlags + # uses a substring test. Add '/' to make sure we match the full filename. + psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", + "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", + "/iterativesolver_single_prec.f90"] + + # Debug + # ===== + ifort.add_flags(["-O2", "-check bounds,uninit", "-no-vec"], "debug") + + # Disable all checks for the files in psrc: + for pattern in psrc: + ifort.add_flags(ContainFlags(pattern, ["-nocheck"]), "safe") + + # Safe + # ==== + # No safe-mode defined for intel?? + + # High + # ==== + # AH - does not run with -O3, so standard setting is -O2 with no checking + ifort.add_flags(["-O2", "-no-vec"], "high") + + # Set up the linker + # ================= + linker = tr.get_tool(Category.LINKER, f"linker-{ifort.name}") + linker = cast(Linker, linker) + + # As default, use nf-config to set NetCDF linker flags. If it's not + # available (or not working properly), the site-specific setup must + # add netcdf definitions. + nf_config = NfConfig() + if nf_config.is_available: + linker.add_lib_flags("netcdf", nf_config.get_linker_flags()) 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 0000000..277eb7f --- /dev/null +++ b/fab/site_specific/default/setup_script_intel_llvm.py @@ -0,0 +1,98 @@ +#!/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 file contains a function that sets the default flags for all +Intel llvm based compilers in the ToolRepository (ifx, ifc). For now, +it's basically a copy of setip_script_intel_classic. + +This function gets called from the default site-specific config file +''' + +import argparse +from typing import cast + +from fab.api import (BuildConfig, Category, Compiler, ContainFlags, Linker, + ToolRepository) + +from nf_config import NfConfig + + +def setup_script_intel_llvm(build_config: BuildConfig, + args: argparse.Namespace) -> None: + # 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() + ifx = tr.get_tool(Category.FORTRAN_COMPILER, "ifx") + ifx = cast(Compiler, ifx) + + if not ifx.is_available: + # This can happen if ifx is not in path (in spack environments). + # To support this common use case, see if mpif90-ifx is available, + # and initialise this otherwise. + ifx = tr.get_tool(Category.FORTRAN_COMPILER, "mpif90-ifx") + ifx = cast(Compiler, ifx) + if not ifx.is_available: + # Since some flags depends on version, the code below requires + # that the intel compiler actually works. + return + + icx = tr.get_tool(Category.C_COMPILER, "icx") + icx = cast(Compiler, icx) + if not icx.is_available: + icx = tr.get_tool(Category.C_COMPILER, "mpicc-icx") + icx = cast(Compiler, icx) + + # The base flags + # ============== + # The following flags will be applied to all modes: + ifx.add_flags(["-g", "-traceback", "-fp-model", "precise"], "base") + ifx.add_flags(ContainFlags("/model_core/", "-recursive"), "base") + ifx.add_flags(ContainFlags("/io/", "-recursive"), "base") + + icx.add_flags(["-g", "-traceback", "-std=gnu99"], "base") + + # The following files have special flags in some modes. ContainFlags + # uses a substring test. Add '/' to make sure we match the full filename. + psrc = ["/conversions.f90", "/pressuresource.f90", "/fftsolver.f90", + "/fftnorth.f90", "/fftpack.f90", "/iterativesolver.f90", + "/iterativesolver_single_prec.f90"] + + # Debug + # ===== + ifx.add_flags(["-O2", "-check bounds,uninit", "-no-vec"], "debug") + + # Disable all checks for the files in psrc: + for pattern in psrc: + ifx.add_flags(ContainFlags(pattern, ["-nocheck"]), "safe") + + # Safe + # ==== + # No safe-mode defined for intel?? + + # High + # ==== + # AH - does not run with -O3, so standard setting is -O2 with no checking + ifx.add_flags(["-O2", "-no-vec"], "high") + + # Set up the linker + # ================= + linker = tr.get_tool(Category.LINKER, f"linker-{ifx.name}") + linker = cast(Linker, linker) + + # As default, use nf-config to set NetCDF linker flags. If it's not + # available (or not working properly), the site-specific setup must + # add netcdf definitions. + nf_config = NfConfig() + if nf_config.is_available: + linker.add_lib_flags("netcdf", nf_config.get_linker_flags()) 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 0000000..36e558c --- /dev/null +++ b/fab/site_specific/default/setup_script_nvidia.py @@ -0,0 +1,97 @@ +#!/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 file contains a function that sets the default flags for the NVIDIA +compilers in the ToolRepository. + +#TODO: +This flags must be checked, atm they are just copied from LFRic. + +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 + +from nf_config import NfConfig + + +def setup_script_nvidia(build_config: BuildConfig, + args: argparse.Namespace) -> None: + # 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 = ["-g", "-traceback", + "-O0", # No optimisations + ] + + # Handle accelerator options: + if args.openacc or args.openmp: + host = args.host.lower() + else: + # Neither openacc nor openmp specified + host = "" + + lib_flags = [] + 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") + + nvfortran.add_flags(flags, "base") + + # Debug + # ===== + nvfortran.add_flags(["-O0", "-fp-model=strict"], "debug") + + # Safe + # ==== + nvfortran.add_flags(["-O2", "-fp-model=strict"], "fast-debug") + + # Production + # ========== + nvfortran.add_flags(["-O4"], "high") + + # Set up the linker + # ================= + linker = tr.get_tool(Category.LINKER, f"linker-{nvfortran.name}") + linker = cast(Linker, linker) + + linker.add_post_lib_flags(lib_flags) + # As default, use nf-config to set NetCDF linker flags. If it's not + # available (or not working properly), the site-specific setup must + # add netcdf definitions. + nf_config = NfConfig() + if nf_config.is_available: + linker.add_lib_flags("netcdf", nf_config.get_linker_flags())