Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
10678fa
#313 First set of changes, introducing new Flag types.
hiker Sep 4, 2025
b03361b
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Oct 7, 2025
ce0e630
#313 Minor code cleanup.
hiker Oct 8, 2025
22bbca7
#313 Renamed Flags into FlagList.
hiker Oct 8, 2025
0b3b568
#313 Removed duplicated test.
hiker Oct 8, 2025
f172226
#313 Replaced FlagsConfig with FlagList, and use FlagList internally …
hiker Oct 8, 2025
a49236b
#313 Take the file path into account when computing the hash of compi…
hiker Oct 9, 2025
4535905
#313 Make flake8 happy.
hiker Oct 9, 2025
f1090bd
#313 Updated testing to cover all of flags.
hiker Oct 9, 2025
d2f156e
#313 Made pattern the first argument of the constructor.
hiker Oct 9, 2025
80d1905
#502 Support ignoring DEPDENDS-ON dependencies, and avoid crash.
hiker Oct 10, 2025
bd612f1
#502 Fixed #todo.
hiker Oct 10, 2025
38a049c
#502 Added standalone unit test for mo.
hiker Oct 10, 2025
64e7d33
Merge branch '502_ignore_mo_dependencies' into 313_unify_flag_handling
hiker Oct 10, 2025
0c92c5c
#502 For DEPENDS ON comments store the original .o name, so it can be…
hiker Oct 11, 2025
41c5956
#502 Removed incorrect parameter documentation.
hiker Oct 12, 2025
46d1a8c
#502 Add support to ignore dependencies to PSyclone step.
hiker Oct 13, 2025
4130738
Merge branch '502_ignore_mo_dependencies' into 313_unify_flag_handling
hiker Oct 13, 2025
93b29f0
#502 Fixed incorrect declaration.
hiker Oct 13, 2025
41ab3b0
Merge branch '502_ignore_mo_dependencies' into 313_unify_flag_handling
hiker Oct 13, 2025
19d8194
#502 Refactored tool so that Tool has no flags anymore.
hiker Oct 13, 2025
28716cb
#502 Removed now unnecessary config argument for run.
hiker Oct 13, 2025
43d206d
#502 Fixed flake8.
hiker Oct 13, 2025
5c33125
#313 Finally remove special handling of flags in compiler.
hiker Oct 13, 2025
c449bc0
#313 Fixed debugging typos.
hiker Oct 13, 2025
7f5b092
Merge branch 'main' into 313_unify_flag_handling
hiker Oct 16, 2025
eb9498b
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Oct 24, 2025
68fcb6f
#525 Add code and test to recognise a Fortran compiler wrapper to be …
hiker Nov 18, 2025
7b8f511
#525 Added additional test to cover additional line.
hiker Nov 18, 2025
88b414a
Merge branch '525_allow_autodetect_of_fortran_compiler_wrapper' into …
hiker Nov 18, 2025
4388dc5
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Nov 20, 2025
a8f0c66
#313 Remove old way of providing path-specific flags per compiler.
hiker Nov 20, 2025
53c2482
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Nov 28, 2025
1fa7adc
#313 Removed __eq__ method, which was only used to simplify testing, …
hiker Nov 28, 2025
a60d1e4
#313 Fixed typing inconsistency.
hiker Nov 28, 2025
983127a
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Jan 13, 2026
c6f7fa1
Merge remote-tracking branch 'origin/main' into 313_unify_flag_handling
hiker Jan 27, 2026
1b13a74
#313 Added documentation for new Flag classes.
hiker Jan 28, 2026
1491039
#313 Minor code cleanups.
hiker Jan 28, 2026
c544a62
#313 Handle missing config file better.
hiker Jan 28, 2026
222f795
#313 Fixed mypy errors.
hiker Jan 28, 2026
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
140 changes: 139 additions & 1 deletion Documentation/source/advanced_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ In not-representative tests, importing tool using ``api`` takes around

The list of functions and classes provided:

.. literalinclude:: ../../source/fab/api/__init__.py
.. literalinclude:: ../../source/fab/api.py
:start-after: __all__ = [
:end-before: ]

Expand Down Expand Up @@ -404,6 +404,144 @@ instance itself, as shown further above, is the better approach.

Path-specific flags
-------------------
A common use case in complex applications is the need to specify
file-specific compiler flags. For example, a file might need to enable
additional optimisations, or disable other optimisations.

Fab uses a set of classes to manage flags consistently across
all tools.

AlwaysFlags
This object represents a list of flags that are always used, i.e.
they are independent of the filename being processed. It provides
templating capabilities, i.e. a set of keywords starting with `$`
will be replaced with a value. The following keywords are defined
if a config file is specified:

- ``$source`` for *<project workspace>/source*
- ``$output`` for *<project workspace>/build_output*
- ``$relative`` for *<the source file's folder>*

These values are taken from the build configuration (if specified).

Example:

.. code-block::
:linenos:
:caption: Creating an AlwaysFlags instance

from fab.api import AlwaysFlags

# Assuming that build_config specifies /some/path/fab
# as project directory for Fab:
af = AlwaysFlags(["-g", "-O2", "-I$source/um/include/other"])
assert (af.get_flags(build_config) ==
["-g", "-O2", "-I/some/path/fab/source/um/include/other"])


MatchFlags (based on ``AlwaysFlags``)
This represents a set of flags that are only applied if a wildcard
search matches the source file to be processed. Note that a wildcard
search must in general match the full path (unless the pattern starts
with a ``*``). The templates explained in the ``AlwaysFlags`` can be
used to create absolute paths in the pattern as well.

Example:

.. code-block::
:linenos:
:caption: Creating a MatchFlags instance

from fab.api import MatchFlags

mf = MatchFlags("/some/dir", "-g")

assert mf.get_flags(file_path=Path("/some/dir")) == ["-g"]
assert mf.get_flags(file_path=Path("/other/some/dir")) == []

# Starting a pattern with * will match a:
mf = MatchFlags("*/some/dir", "-g")
assert mf.get_flags(file_path=Path("/other/some/dir")) == ["-g"]


ContainFlags (based on ``AlwaysFlags``)
Flags that are only applied if the file path contains the specified
string. The difference to ``MatchFlags`` is that ``ContainFlags`` do not
need the full path to be specified.

Example:

.. code-block::
:linenos:
:caption: Creating a ContainFlags instance

from fab.api import ContainFlags

cf = ContainFlags(pattern="/some/dir", "-g")

assert cf.get_flags(file_path=Path("/not/in/root/some/dir")) == ["-g"]
assert cf.get_flags(file_path=Path("/some/other")) == []

FlagList:
This class represents the main class to manage a set of flags.
Each of its members is either an ``AlwaysFlags``, ``MatchFlags``
or ``ContainFlags``. When the list is evaluated, each individual
flag instance is evaluated and will depending on the processed
file add flags or not.

This class provides convenient constructor which will convert
a string or list of strings to an ``AlwaysFlags`` instance.
It also provides backwards compatibility to the previous
``AddFlag`` object (see :ref:`add_flag`):

Example:

.. code-block::
:linenos:
:caption: Creating a FlagList

from fab.api import ContainFlags, FlagList
from fab.build_config import AddFlags

cf = ContainFlags(pattern="/some/dir", "-g")
add_flags = AddFlags(match="/some/pattern", flags=["-g", "-O0"])
# This will trigger conversion of add_flags to MathFlags
flag_list = FlagList(add_flags=[add_flags])

ProfileFlags:
Tools have a ``ProfileFlags`` as member, which they use to manage
all their flags. Depending on the selected profile, this ``ProfileFlags``
members returns the ``FlagList`` to use. As explained earlier, each of
the members of FlagList can be any of the previous flag types to give
flexibility for a user to specify the compilation flags. See
:ref:`Advanced Flags` for details about profiling modes.

Example:

.. code-block::
:linenos:
:caption: Setting up compiler for the UM

tr = ToolRepository()
ifx = tr.get_tool(Category.FORTRAN_COMPILER, "ifx")

# These will be converted to an AlwaysFlag:
ifx.add_flags(["-stand", "f08"], "base")

# ifx 2025.2 has no omp_version in omp_lib, so we
# have to disable OpenMP:
if (2025, 2) <= ifx.get_version() < (2025, 3):
ifx.add_flags(ContainFlags("/um_shell.", ["-qno-openmp"]), "base")

# Prevent loss of comparison across decompositions when OpenMP on
ifx.add_flags(ContainFlags("/ni_imp_ctl.", "-qno-openmp"), "base")



.. _add_flag:

Path-specific flags - old style
-------------------------------

For preprocessing and compilation, we sometimes need to specify flags
*per-file*. These steps accept both common flags and *path specific* flags.
Expand Down
7 changes: 7 additions & 0 deletions source/fab/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,20 @@
from fab.tools.category import Category
from fab.tools.compiler import Compiler, Ifort
from fab.tools.compiler_wrapper import CompilerWrapper
from fab.tools.flags import AlwaysFlags, ContainFlags, FlagList, MatchFlags
from fab.tools.linker import Linker
from fab.tools.tool import Tool
from fab.tools.tool_box import ToolBox
from fab.tools.tool_repository import ToolRepository
from fab.tools.shell import Shell
from fab.util import common_arg_parser
from fab.util import file_checksum, log_or_dot, TimerLogger
from fab.util import get_fab_workspace
from fab.util import input_to_output_fpath

__all__ = [
"AddFlags",
"AlwaysFlags",
"analyse",
"archive_objects",
"ArtefactSet",
Expand All @@ -53,10 +56,12 @@
"CompilerWrapper",
"compile_c",
"compile_fortran",
"ContainFlags",
"c_pragma_injector",
"Exclude",
"fcm_export",
"file_checksum",
"FlagList",
"get_fab_workspace",
"git_checkout",
"grab_folder",
Expand All @@ -69,12 +74,14 @@
"link_exe",
"link_shared_object",
"log_or_dot",
"MatchFlags",
"preprocess_c",
"preprocess_fortran",
"preprocess_x90",
"psyclone",
"root_inc_files",
"run_mp",
"Shell",
"step",
"SuffixFilter",
"TimerLogger",
Expand Down
64 changes: 16 additions & 48 deletions source/fab/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ def __init__(self, project_label: str,
self._openmp = openmp
if profile is None:
# An empty string is used for non-profiled flags
self._profile = ""
self.set_profile("")
else:
self._profile = profile
self.set_profile(profile)
self.two_stage = two_stage
self.verbose = verbose
compiler = tool_box.get_tool(Category.FORTRAN_COMPILER, mpi=mpi,
Expand Down Expand Up @@ -223,6 +223,13 @@ def profile(self) -> str:
''':returns: the name of the compiler profile to use.'''
return self._profile

def set_profile(self, profile: str) -> None:
"""Sets the compilation profile.

:param profile: the name of the profile.
"""
self._profile = profile.lower()

def add_current_prebuilds(self, artefacts: Iterable[Path]):
"""
Mark the given file paths as being current prebuilds, not to be
Expand Down Expand Up @@ -288,10 +295,16 @@ def _finalise_metrics(self, start_time, steps_timer):


# todo: better name? perhaps PathFlags?
# todo: This is going to be replaced with tools.flags.MatchFlags

class AddFlags():
"""
Add command-line flags when our path filter matches.
Generally used inside a :class:`~fab.build_config.FlagsConfig`.
This class is deprecated, use the new
:class:`~fab.tools.flags.FlagList` and
:class:`~fab.tools.flags.MatchFlag` instead.
The compile and preprocess steps will convert AddFlags into
MatchFlags

"""
def __init__(self, match: str, flags: List[str]):
Expand Down Expand Up @@ -347,48 +360,3 @@ def run(self, fpath: Path, input_flags: List[str], config):

# add our flags
input_flags += add_flags


class FlagsConfig():
"""
Return command-line flags for a given path.

Simply allows appending flags but may evolve to also replace and
remove flags.

"""
def __init__(self, common_flags: Optional[List[str]] = None,
path_flags: Optional[List[AddFlags]] = None):
"""
:param common_flags:
List of flags to apply to all files. E.g `['-O2']`.
:param path_flags:
List of :class:`~fab.build_config.AddFlags` objects which apply
flags to selected paths.

"""
self.common_flags = common_flags or []
self.path_flags = path_flags or []

# todo: there's templating both in this method and the run method it calls.
# make sure it's all properly documented and rationalised.
def flags_for_path(self, path: Path, config):
"""
Get all the flags for a given file, in a reproducible order.

:param path:
The file path for which we want command-line flags.
:param config:
The config contains the source root and project workspace.

"""
# We COULD make the user pass these template params to the constructor
# but we have a design requirement to minimise the config burden on
# the user, so we take care of it for them here instead.
params = {'source': config.source_root, 'output': config.build_output}
flags = [Template(i).substitute(params) for i in self.common_flags]

for flags_modifier in self.path_flags:
flags_modifier.run(path, flags, config=config)

return flags
11 changes: 2 additions & 9 deletions source/fab/fab_base/fab_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,19 +673,15 @@ def compile_c_step(
all C files. Optionally, common flags, path-specific flags and
alternative source can also be passed to Fab for compilation.
"""
site_path_flags: List[AddFlags] = []
if self._site_config:
site_path_flags = self._site_config.get_path_flags(self._config)
if not common_flags:
common_flags = []
assert isinstance(common_flags, list)
if not path_flags:
path_flags = []

compile_c(self.config,
common_flags=(common_flags +
self.c_compiler_flags_commandline),
path_flags=path_flags + site_path_flags)
path_flags=path_flags)

def compile_fortran_step(
self,
Expand All @@ -701,17 +697,14 @@ def compile_fortran_step(
:param path_flags: optional list of path-specific flags to be passed
to Fab compile_fortran, default is None.
"""
site_path_flags: List[AddFlags] = []
if self._site_config:
site_path_flags = self._site_config.get_path_flags(self._config)
if not common_flags:
common_flags = []
if not path_flags:
path_flags = []
compile_fortran(self.config,
common_flags=(common_flags +
self.fortran_compiler_flags_commandline),
path_flags=path_flags + site_path_flags)
path_flags=path_flags)

def link_step(self) -> None:
"""
Expand Down
Loading