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.

build_it#
 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.

build_it.py#
 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.

build_it.py#
 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 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.

build_it.py#
 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

build_it.py#
 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.

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.