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 "all_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 ‘all_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 "psyclone_output"
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 several collections:
.f90
found in the source.F90
we pre-processed into.f90
preprocessed c
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"
.
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.