Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -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 |
1 change: 1 addition & 0 deletions fab/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fab-workspace
85 changes: 85 additions & 0 deletions fab/README.md
Original file line number Diff line number Diff line change
@@ -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``.
169 changes: 169 additions & 0 deletions fab/fab_monc.py
Original file line number Diff line number Diff line change
@@ -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()
37 changes: 37 additions & 0 deletions fab/nf_config.py
Original file line number Diff line number Diff line change
@@ -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()
Loading