How to Write a Build Configuration#
This page walks through the process of writing a build script.
Configuration File#
Here’s a simple configuration without any steps.
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.build_config import BuildConfig
5
6logger = getLogger('fab')
7
8if __name__ == '__main__':
9
10 with BuildConfig(project_label='<project label>') as state:
11 pass
If we want to run the build script from the command line,
we give it executable permission with the command chmod +x build_it
.
We also add the shebang directive on line 1,
telling our computer it’s a Python script.
Pick a project label. Fab creates a project workspace with this name.
Source Code#
Let’s tell Fab where our source code is.
We use the find_source_files()
step for this.
We can point this step to a source folder, however, because Fab can sometimes
create artefacts alongside the source [1], we usually copy the source into the
project workspace first using a grab
step.
A grab step will copy files from a folder or remote repo into a folder called “source” within the project workspace.
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.build_config import BuildConfig
5from fab.steps.find_source_files import find_source_files
6from fab.steps.grab.folder import grab_folder
7
8logger = getLogger('fab')
9
10if __name__ == '__main__':
11
12 with BuildConfig(project_label='<project label>') as state:
13 grab_folder(state, src='<path to source folder>')
14 find_source_files(state)
Note
Fab tries to minimise user input by providing sensible defaults. In this case, the user doesn’t have to specify where the code goes. The grab and find_source_files steps already know what to do by default. Sensible defaults can be overridden.
Please see the documentation for find_source_files()
for more information,
including how to exclude certain source code from the build. More grab steps can be found in the grab
module.
After the find_source_files step, there will be a collection called "INITIAL_SOURCE"
, in the artefact store.
Preprocess#
Next we want to preprocess our source code. Preprocessing resolves any #include and #ifdef directives in the code, which must happen before we analyse it.
Steps generally create and find artefacts in the Artefact Store, arranged into named collections.
The preprocess_fortran()
automatically looks for Fortran source code in a collection named ‘INITIAL_SOURCE’,
which is the default output from the preceding :funcfind_source_files step.
It filters just the (uppercase) .F90
files.
Note
Uppercase .F90
are preprocessed into lowercase .f90
.
The Fortran preprocessor will read the FPP environment variable to determine which tool to call.
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.build_config import BuildConfig
5from fab.steps.find_source_files import find_source_files
6from fab.steps.grab.folder import grab_folder
7from fab.steps.preprocess import preprocess_fortran
8
9logger = getLogger('fab')
10
11if __name__ == '__main__':
12
13 with BuildConfig(project_label='<project label>') as state:
14 grab_folder(state, src='<path to source folder>')
15 find_source_files(state)
16 preprocess_fortran(state)
Preprocessed files are created in the ‘build_output’ folder, inside the project workspace.
After the fortran_preprocessor step, there will be a collection called "preprocessed_fortran"
, in the artefact store.
PSyclone#
If you want to use PSyclone to do code transformation and pre-processing (see stfc/PSyclone),
you must run preprocess_x90()
and psyclone()
,
before you run the analyse()
step below.
- For
preprocess_x90()
: You can pass in common_flags list as an argument.
- For
- For
psyclone()
: You can pass in:
kernel file roots to kernel_roots,
a function to get transformation script to transformation_script (see examples in
~fab.run_configs.lfric.gungho.py
and~fab.run_configs.lfric.atm.py
),command-line arguments to cli_args,
override for input files to source_getter,
folders containing override files to overrides_folder.
- For
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.build_config import BuildConfig
5from fab.steps.find_source_files import find_source_files
6from fab.steps.grab.folder import grab_folder
7from fab.steps.preprocess import preprocess_fortran
8from fab.steps.psyclone import psyclone, preprocess_x90
9
10logger = getLogger('fab')
11
12if __name__ == '__main__':
13
14 with BuildConfig(project_label='<project label>') as state:
15 grab_folder(state, src='<path to source folder>')
16 find_source_files(state)
17 preprocess_fortran(state)
18 preprocess_x90(state)
19 psyclone(state)
After the psyclone step, two new source files will be created for each .x90 file in the ‘build_output’ folder.
These two output files will be added under FORTRAN_BUILD_FILES
collection to the artefact store.
Analyse#
We must analyse()
the source code to determine which
Fortran files to compile, and in which order.
The Analyse step looks for source to analyse in two collections:
FORTRAN_BUILD_FILES
, which contains all.f90
found in the source, all.F90
files we pre-processed into.f90
, and files created by any additional step (e.g. PSyclone).C_BUILD_FILES
, all preprocessed c files.
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.steps.analyse import analyse
5from fab.build_config import BuildConfig
6from fab.steps.find_source_files import find_source_files
7from fab.steps.grab.folder import grab_folder
8from fab.steps.preprocess import preprocess_fortran
9from fab.steps.psyclone import psyclone, preprocess_x90
10
11logger = getLogger('fab')
12
13if __name__ == '__main__':
14
15 with BuildConfig(project_label='<project label>') as state:
16 grab_folder(state, src='<path to source folder>')
17 find_source_files(state)
18 preprocess_fortran(state)
19 preprocess_x90(state)
20 psyclone(state)
21 analyse(state, root_symbol='<program>')
Here we tell the analyser which Root Symbol we want to build into an executable.
Alternatively, we can use the find_programs
flag for Fab to discover and build all programs.
After the Analyse step, there will be a collection called BUILD_TREES
, in the artefact store.
Compile and Link#
The compile_fortran()
step compiles files in
the BUILD_TREES
collection. The link_exe()
step
then creates the executable.
1#!/usr/bin/env python3
2from logging import getLogger
3
4from fab.steps.analyse import analyse
5from fab.build_config import BuildConfig
6from fab.steps.compile_fortran import compile_fortran
7from fab.steps.find_source_files import find_source_files
8from fab.steps.grab.folder import grab_folder
9from fab.steps.link import link_exe
10from fab.steps.preprocess import preprocess_fortran
11from fab.steps.psyclone import psyclone, preprocess_x90
12
13logger = getLogger('fab')
14
15if __name__ == '__main__':
16
17 with BuildConfig(project_label='<project label>') as state:
18 grab_folder(state, src='<path to source folder>')
19 find_source_files(state)
20 preprocess_fortran(state)
21 preprocess_x90(state)
22 psyclone(state)
23 analyse(state, root_symbol='<program>')
24 compile_fortran(state)
25 link_exe(state)
After the link_exe()
step, the executable name can be found in a collection called EXECUTABLES
.
ArtefactStore#
Each build configuration contains an artefact store, containing various
sets of artefacts. The artefact sets used by Fab are defined in the
enum ArtefactSet
. The most important sets are FORTRAN_BUILD_FILES
,
C_BUILD_FILES
, which will always contain all known source files that
will need to be analysed for dependencies, compiled, and linked. All existing
steps in Fab will make sure to maintain these artefact sets consistently,
for example, if a .F90
file is preprocessed, the .F90
file in
FORTRAN_BUILD_FILES
will be replaced with the corresponding preprocessed
.f90
file. Similarly, new files (for examples created by PSyclone)
will be added to FORTRAN_BUILD_FILES
. A user script can adds its own
artefacts using strings as keys if required.
The exact flow of artefact sets is as follows. Note that any artefact sets mentioned here can typically be overwritten by the user, but then it is the user’s responsibility to maintain the default artefact sets (or change them all):
find_source_files()
will add all source files it finds toINITIAL_SOURCE
(by default, can be overwritten by the user). Any.F90
and.f90
file will also be added toFORTRAN_BUILD_FILES
, any.c
file toC_BUILD_FILES
, and any.x90
or.X90
file toX90_BUILD_FILES
. It can be called several times if files from different root directories need to be added, and it will automatically update the*_BUILD_FILES
sets.Any user script that creates new files can add files to
INITIAL_SOURCE
if required, but also to the corresponding*_BUILD_FILES
. This will happen automatically iffind_source_files()
is called to add these newly created files.If
c_pragma_injector()
is being called, it will handle all files inC_BUILD_FILES
, and will replace all the original C files with the newly created ones. For backward compatibility it will also store the new objects in thePRAGMAD_C
set.If
preprocess_c()
is called, it will preprocess all files inC_BUILD_FILES
(at this stage typically preprocess the files in the original source folder, writing the output files to the build folder), and update that artefact set accordingly. For backward compatibility it will also store the preprocessed files inPREPROCESSED_C
.If
preprocess_fortran()
is called, it will preprocess all files inFORTRAN_BUILD_FILES
that end on.F90
, creating new.f90
files in the build folder. These files will be added toPREPROCESSED_FORTRAN
. Then the original.F90
are removed fromFORTRAN_BUILD_FILES
, and the new preprocessed files (which are inPREPROCESSED_FORTRAN
) will be added. Then any.f90
files that are not already in the build folder (an example of this are files created by a user script) are copied from the original source folder into the build folder, andFORTRAN_BUILD_FILES
is updated to use the files in the new location.If
preprocess_x90()
is called, it will similarly preprocess all.X90
files inX90_BUILD_FILES
, creating the output files in the build folder, and replacing the files inX90_BUILD_FILES
.If
psyclone()
is called, it will process all files inX90_BUILD_FILES
and add any newly created file toFORTRAN_BUILD_FILES
, and removing them fromX90_BUILD_FILES
.The
analyse()
step analyses all files inFORTRAN_BUILD_FILES
andC_BUILD_FILES
, and add all dependencies toBUILD_TREES
.The
compile_c()
andcompile_fortran()
steps will compile all files fromC_BUILD_FILES
andFORTRAN_BUILD_FILES
, and add them toOBJECT_FILES
.If
archive_objects()
is called, it will create libraries based onOBJECT_FILES
, adding the libraries toOBJECT_ARCHIVES
.If
link_exe()
is called, it will either useOBJECT_ARCHIVES
, or if this is empty, useOBJECT_FILES
, create the binaries, and add them toEXECUTABLES
.
Flags#
Preprocess, compile and link steps usually need configuration to specify command-line arguments to the underlying tool, such as symbol definitions, include paths, optimisation flags, etc. See also Advanced Flags.
C Code#
Fab comes with C processing steps.
The preprocess_c()
and compile_c()
Steps
behave like their Fortran equivalents.
However preprocessing C currently requires a preceding step called the
c_pragma_injector()
. This injects markers
into the C code so Fab is able to deduce which inclusions are user code and
which are system code. This allows system dependencies to be ignored.
See also Advanced C Code
Further Reading#
More advanced configuration topics are discussed in Advanced Configuration.
You can see more complicated configurations in the developer testing directory.