Site-Specific Configuration#
A site might have compilers that Fab doesn’t know about, or prefers
a different compiler from the Fab default. Fab abstracts the compilers
and other programs required during building as an instance of a
Tool
class. All tools that Fab knows about, are
available in a ToolRepository
.
That will include tools that might not be available on the current system.
Each tool belongs to a certain category of
Category
. A ToolRepository can store
several instances of the same category.
At build time, the user has to create an instance of
ToolBox
and pass
it to the BuildConfig
object. This toolbox
contains all the tools that will be used during the build process, but
it can only store one tool per category. If a certain tool should not
be defined in the toolbox, the default from the ToolRepository will
be used. This is useful for many standard tools like git, rsync
etc that de-facto will never be changed.
Note
If you need to use for example different compilers for
different files, you would implement this as a meta-compiler:
implement a new class based on the existing
Compiler
class,
which takes two (or more) compiler instances. Its
compile_file()
method can then decide (e.g. based on the path of the file to
compile, or a hard-coded set of criteria) which compiler to use.
Category#
All possible categories are defined in
Category
. If additional categories
should be required, they can be added.
Tool#
Each tool must be derived from Tool
.
The base class provides a run method, which any tool can
use to execute a command in a shell. Typically, a tool will
provide one (or several) custom commands to be used by the steps.
For example, a compiler instance provides a
compile_file()
method.
This makes sure that no tool-specific command line options need
to be used in any Fab step, which will allow the user to replace any tool
with a different one.
New tools can easily be created, look at
Gcc
or
Icc
. Typically, they can just be
created by providing a different set of parameters in the
constructor.
This also allows compiler wrappers to be easily defined. For example, if you want to use mpif90 as compiler, which is a MPI-specific wrapper for ifort, you can create this class as follows:
1from fab.tools import Ifort
2
3class MpiF90(Ifort):
4 '''A simple compiler wrapper'''
5 def __init__(self):
6 super().__init__(name="mpif90-intel",
7 exec_name="mpif90")
Note
In ticket 312 a better implementation of compiler wrapper will be implemented.
Tool Repository#
The ToolRepository
implements
a singleton to access any tool that Fab knows about. A site-specific
startup section can add more tools to the repository:
1from fab.tools import ToolRepository
2
3# Assume the MpiF90 class as shown in the previous example
4
5tr = ToolRepository()
6tr.add_tool(MpiF90) # the tool repository will create the instance
Compiler and linker objects define a compiler suite, and the ToolRepository
provides
set_default_compiler_suite()
which allows you to change the defaults for compiler and linker with
a single call. This will allow you to easily switch from one compiler
to another. If required, you can still change any individual compiler
after setting a default compiler suite, e.g. you can define intel-classic
as default suite, but set the C-compiler to be gcc.
Tool Box#
The class ToolBox
is used to provide
the tools to be used by the build environment, i.e. the
BuildConfig object:
1from fab.tools import Category, ToolBox, ToolRepository
2
3tr = ToolRepository()
4tr.set_default_compiler_suite("intel-classic")
5tool_box = ToolBox()
6ifort = tr.get_tool(Category.FORTRAN_COMPILER, "ifort")
7tool_box.add_tool(ifort)
8c_compiler = tr.get_default(Category.C_COMPILER)
9tool_box.add_tool(c_compiler)
10
11config = BuildConfig(tool_box=tool_box,
12 project_label=f'lfric_atm-{ifort.name}', ...)
The advantage of finding the compilers to use in the tool box is that it allows a site to replace a compiler in the tool repository (e.g. if a site wants to use an older gfortran version, say one which is called gfortran-11). They can then remove the standard gfortran in the tool repository and replace it with a new gfortran compiler that will call gfortran-11 instead of gfortran. But a site can also decide to not support a generic gfortran call, instead adding different gfortran compiler with a version number in the name.
If a tool category is not defined in the ToolBox, then the default tool from the ToolRepository will be used. Therefore, in the example above adding ifort is not strictly necessary (since it will be the default after setting the default compiler suite to intel-classic), and c_compiler is the default as well. This feature is especially useful for the many default tools that Fab requires (git, rsync, ar, …).
1tool_box = ToolBox()
2default_c_compiler = tool_box.get_tool(Category.C_COMPILER)
There is special handling for compilers and linkers: the build configuration stores the information if an MPI and/or OpenMP build is requested. So when a default tool is requested by the ToolBox from the ToolRepository (i.e. when the user has not added specific compilers or linkers), this information is taken into account, and only a compiler that will fulfil the requirements is returned. For example, if you have gfortran and mpif90-gfortran defined in this order in the ToolRepository, and request the default compiler for an MPI build, the mpif90-gfortran instance is returned, not gfortran. On the other hand, if no MPI is requested, an MPI-enabled compiler might be returned, which does not affect the final result, since an MPI compiler just adds include- and library-paths.
TODO#
At this stage compiler flags are still set in the corresponding Fab steps, and it might make more sense to allow their modification and definition in the compiler objects. This will allow a site to define their own set of default flags to be used with a certain compiler by replacing or updating a compiler instance in the Tool Repository