Build Automation

The build process of a Fortran program or library can be automated by using build utilities that specify all steps necessary for compilation, linking, and installation of a project.

On Unix, Makefiles are a common way to compile from source. Except for simple projects, Makefiles are not portable and not platform-independent, as they have to target a specific environment (compilers, libraries, paths, …). GNU make and BSD make are two variants that provide extensions to the POSIX standard, albeit not compatible to each other. It is therefore preferable to rely on POSIX rules only.

Other build systems bypass these limitations by generating Makefiles with respect to the currently used environment, or by using an independent build backend. Fortran is usually supported by the most common build automation tools, such as:

The Fortran Package Manager (fpm) is specifically written for Fortran projects, while being developed in Fortran itself.

Make

Makefiles are an easy way to maintain the build process of a Fortran project. Once written, the file is run by the make utility (either BSD make or GNU make) that will execute the declared steps, such as compiling the source code and linking the object files.

The following basic Makefile below is used to compile an example Fortran program example.f90 with GNU Fortran. Please note the mandatory hard tabs in the source.

.POSIX:

# Parameters:
#
#       FC          -   Fortran compiler.
#       SRC         -   Source file(s).
#       TARGET      -   Build target name (executable).

FC     = gfortran11
SRC    = example.f90
TARGET = example

.PHONY: all clean run

all: $(TARGET)

$(TARGET):
	$(FC) -o $(TARGET) $(SRC)

clean:
	rm $(TARGET)

run:
	./$(TARGET)

Save the file as Makefile in the directory that contains the source code file example.f90. Execute make or make all inside the directory to compile the example:

$ make
gfortran11 -o example example.f90

To change the compiler to Flang, run:

$ make FC=flang

Or, using Intel Fortran:

$ make FC=ifort

Execute the binary with make run, or delete the target with make clean.

Linking

The Makefile below also links the Fortran program source example.f90 against required dependencies (see ncurses for more information):

.POSIX:

# Parameters:
#
#       FC          -   Fortran compiler.
#       PREFIX      -   Include and library search path prefix.
#       FFLAGS      -   Fortran compiler flags.
#       LDFLAGS     -   Linker flags.
#       LDLIBS      -   Linker libraries.
#       SRC         -   Source file(s).
#       TARGET      -   Build target name.

FC      = flang
FFLAGS  = -Wall
PREFIX  = /usr/local
LDFLAGS = -I$(PREFIX)/include/ -L$(PREFIX)/lib/
LDLIBS  = libm_ncurses.a -lncurses
SRC     = example.f90
TARGET  = example

.PHONY: all clean

all: $(TARGET)

$(TARGET):
	$(FC) $(FFLAGS) $(LDFLAGS) -o $(TARGET) $(SRC) $(LDLIBS)

clean:
	rm $(TARGET) *.o *.mod

The variables can be overwritten through command-line arguments. For instance, on Linux, we would have to run:

$ make PREFIX=/usr

CMake

Using CMake to generate a clean Makefile makes it possible to take the used computer platform into account or to set custom build options. In the following example, the source file src/main.f90 will be compiled and statically linked against a library in src/util.f90.

Please be aware that the ending of all Fortran source files must either be set to .f90 or .f95 to be recognised by CMake. The CMake configuration has to be saved to file CMakeLists.txt in your workspace directory:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(Example)
set(VERSION 1.0)

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE RELEASE)
endif()

if(CMAKE_Fortran_COMPILER MATCHES "gfortran*")
    set(CMAKE_SKIP_BUILD_RPATH            FALSE)
    set(CMAKE_BUILD_WITH_INSTALL_RPATH    TRUE)
    set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

    set(CMAKE_Fortran_FLAGS         "${CMAKE_Fortran_FLAGS} -std=f2003 -fimplicit-none")
    set(CMAKE_Fortran_FLAGS_DEBUG   "-Wall -O0 -g3 -fbounds-check")
    set(CMAKE_Fortran_FLAGS_RELEASE "-O2")
endif()

set(TARGET example)
set(SOURCE_FILES src/main.f90)

add_library(util STATIC src/util.f90)

add_executable(${TARGET} ${SOURCE_FILES})
target_link_libraries(${TARGET} util)
set_target_properties(${TARGET} PROPERTIES LINKER_LANGUAGE Fortran)

We first create our build directory build/, change into it, and generate a Makefile with CMake. Finally, we build the project with make:

$ mkdir build
$ cd build/
$ cmake ..
$ make

We can force a specific compiler by using the -DCMAKE_Fortran_COMPILER flag:

$ cmake -DCMAKE_Fortran_COMPILER=gfortran11 -DCMAKE_INSTALL_RPATH=/usr/local/lib/gcc11/ ..

xmake

The platform-independend xmake build utility is based on Lua and supports multiple programming languages, including Fortran. It is not related to the older XMake. In comparision to other build systems, xmake is more lightweight, consisting of only a single executable with no further dependencies.

On FreeBSD, simply compile the program from source using GNU make:

$ git clone --recurse-submodules https://github.com/xmake-io/xmake
$ cd xmake/
$ gmake build
$ doas gmake install PREFIX=/usr/local

On Linux, run make and sudo instead of gmake and doas. The PREFIX parameter may be omitted. The compiled xmake executable will be copied to /usr/local/bin/.

Inside your Fortran workspace directory, create a new build script named xmake.lua, containing:

-- xmake.lua
target("example")
    set_kind("binary")
    set_languages("fortran")
    add_files("src/util.f90")
    add_files("src/main.f90")

The Fortran program is split into the files src/main.f90 and src/util.f90. Just run xmake inside the workspace directory to build an executable:

$ xmake
[ 46%]: compiling.release src/util.f90
[ 60%]: compiling.release src/main.f90
[ 62%]: linking.release example
[100%]: build ok!

The default output directory is build/. Fortran modules are stored in build/.objs/<name>/<platform>/<architecture>/. We can change the module files directory to, for example, build/ by setting the value fortran.moduledir to the according path:

set_values("fortran.moduledir", "$(buildir)")

We may also build static and shared libraries with xmake. The following xmake script will output the static library libexample.a and the associated Fortran module files to build/:

-- xmake.lua
target("example")
    set_kind("static")
    set_languages("fortran")
    add_files("src/library.f90")
    set_values("fortran.moduledir", "$(buildir)")

Fortran Package Manager

The Fortran Package Manager (fpm) is a build utility aimed to become the default package manager for Fortran. The protoype version was initially written in Haskell and is now replaced by a Fortran implementation. The project is in early development, but can already be used to compile and package applications or libraries in Fortran.

Installation

To build fpm, clone the GitHub repository and run the installation script:

$ git clone https://github.com/fortran-lang/fpm
$ cd fpm/
$ ./install.sh

A Fortran 2008 compiler is required for this step. The binary is installed to ${HOME}/.local/bin/ by default. Make sure the path is in your $PATH environment variable, for instance:

$ export PATH=${PATH}:${HOME}/.local/bin/
$ echo $PATH

Add the directory to the $PATH variable in your profile to make the changes permanent.

Environment VariableDefinesOverridden by
FPM_FCFortran compiler path--compiler
FPM_CCC compiler path--c-compiler
FPM_FFLAGSFortran compiler flags--flag
FPM_CFLAGSC compiler flags--c-flag
FPM_ARArchiver path--archiver
FPM_LDFLAGSLinker flags--link-flag
Tab. 1: Environment variables and command-line arguments

Packages

Fortran projects using FPM have to follow a recommended file system structure if not configured otherwise. The source code of Fortran applications is normally saved to app/, while Fortran module source files must be located in src/. The configuration file fpm.toml in the project workspace directory contains the package description and some meta information. Non-default directory paths can be set in the file.

The workspace of an example Fortran project may include the following files and directories:

The configuration file fpm.toml contains a basic package description:

# fpm.toml
name = "example"
version = "0.1.0"
license = "ISC"
author = "Jane Doe"
maintainer = "mail@example.com"
copyright = "2020 Jane Doe"

We just have to run fpm build to compile the target:

$ fpm build --profile=debug
# gfortran (for build/gfortran_debug/example/src_library.f90.o build/gfortran_debug/example/library.mod)
# gfortran (for build/gfortran_debug/example/src_util.f90.o build/gfortran_debug/example/util.mod)
# ar (for build/gfortran_debug/example/libexample.a)
ar: warning: creating build/gfortran_debug/example/libexample.a
# gfortran (for build/gfortran_debug/app/app_main.f90.o)
# gfortran (for build/gfortran_debug/app/example)

Choose between the build profile debug and release. Afterwards, start the executable with fpm run:

$ fpm run

Compiler and linker flags can be set through environment variables and/or command-line arguments (table 1).

Further Reading

References