Build Automation

The build process of Fortran program or library can be automated by severial build tools. A common way on Unix are Makefiles, which specify all steps for compilation, linking, and installation of your project.

In general, Makefiles are not platform-independent, as they usually target a specific environment. Therefore, it may be necessary to write distiguished Makefiles for each targeted system. Makefiles are a POSIX standard, but the various implementations, like BSD make and GNU make, provide several extensions, that make the result depend on one of them. In this case, a Makefile written for BSD make may not be accepted by GNU make, or vice versa.

The CMake build system, for instance, bypasses these limitations by generating Makefiles with respect to the currently used environment and the compilers installed.

One can choose between several build utilities that feature Fortran support, among them:

Make

Makefiles are an easy way to maintain the build process of a Fortran project. Once written, the file can executed by the make utility (either BSD make or GNU make) that will take all the defined steps, like compiling the source code and linking the object files.

The following very simplistic 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     = gfortran10
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
gfortran10 -o example example.f90

To change the defined compiler to Flang, run:

$ make FC=flang

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

Linking

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

.POSIX:

# Parameters:
#
#       FC          -   Fortran compiler.
#       FFLAGS      -   Fortran compiler flags.
#       LDFLAGS     -   Linker flags.
#       LDLIBS      -   Linker libraries.
#       SRC         -   Source file(s).
#       TARGET      -   Build target name.

FC      = flang
FFLAGS  = -Wall
LDFLAGS = -I/usr/local/include/ -L/usr/local/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

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=gfortran10 -DCMAKE_INSTALL_RPATH=/usr/local/lib/gcc10/ ..

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 is written in Haskell and will be replaced by a Fortran implementation in future. The project is in early development, but can already be used to compile and package applications or libraries in Fortran.

Installation

To build the Fortran Package Manager, 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.

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

References