Fortran Package Manager (fpm)

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. fpm is not part of the standard: it is a community tool of the fortran-lang project, written in Fortran, and everything on this page describes fpm 0.13.0, released on 2026-02-17 (the binary used here prints 0.13.0, alpha for fpm --version, the label this release carries). Every fpm project on the page was built and run with fpm 0.13.0 using GNU Fortran 16.2 and, where stated, LLVM Flang 23, inside a Linux container; the output shown is real output, abridged where it is long, and the Fortran sources compile warning-free under gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (passed to fpm with --flag). Parts that need services or tools that are not in the container (the package registry, publishing, HDF5, NetCDF and MINPACK metapackages, GitHub Actions, the installation channels) are documented from the official fpm pages and are marked as not run. The source of a build is always one of the heatflow modules introduced in Getting Started and used throughout this section (heatflow_kinds, heatflow_grid, heatflow_solver and the rest, see Modules and Submodules).

A Fortran build has three jobs: find the sources, compile them in an order that respects module dependencies, and link the result. The standard says nothing about any of it, and Fortran’s module files (.mod) make the order a real problem (see the section on module files in Getting Started). fpm solves it for the common case by reading the sources: it scans every use, submodule and include statement, builds the dependency graph itself, and needs only a short TOML manifest, fpm.toml, and a conventional directory layout. It also downloads and builds dependencies from git repositories, local paths or a registry, which is what the "package manager" part of the name means. The two other tools in the section that do the same job from a different angle, CMake and Meson, are compared on Building with Make, CMake and Meson; the compiler flags fpm passes are explained on Compilers and Compiler Options; the unit-test framework fpm projects usually use, test-drive, is on Testing, Debugging and Profiling; and the standard library that fpm can fetch as a dependency is on Fortran stdlib.

flowchart LR M["fpm.toml
+ fpm command line"] --> P["Profile and features
flags, macros, dependencies"] P --> D["Resolve dependencies
path, git, registry, metapackages"] D --> S["Scan sources
use, submodule, include"] S --> G["Dependency graph
module order"] G --> C["Compile
one .o and .mod per file"] C --> A["Archive
libNAME.a"] A --> L["Link
apps, tests, examples"] L --> X["run, test or install"]

Installing fpm

fpm is one self-contained executable. The official installation page lists these channels (documented from the page, not run here; the container used for this page carries fpm 0.13.0 built from the release).

Channel How

Release binaries

download the file for your system from the GitHub release page (Linux, macOS and Windows on x86-64, and macOS ARM64 artifacts since 0.13.0), make it executable with chmod +x, optionally rename it to fpm, and compare it with the published .sha256 file

MSYS2 (Windows)

pacman -S git mingw-w64-x86_64-gcc-fortran mingw-w64-x86_64-fpm in a MinGW64 or UCRT64 terminal

WinGet (Windows)

winget install FortranLang.fpm

Homebrew (macOS)

brew tap fortran-lang/homebrew-fortran and brew install fpm

MacPorts

sudo port install fpm

PyPI

pipx install fpm (pipx keeps a private environment)

Conda

conda install fpm from conda-forge

Spack

spack install fpm

Arch Linux AUR

fortran-fpm-bin (the release binary) or fortran-fpm (bootstrapped from source)

OpenBSD

the devel/fpm port

From source

./install.sh bootstraps fpm with just a Fortran compiler, git and network access and installs it to $HOME/.local/bin; by hand, compile the single-source fpm.F90 from the current release with gfortran and let that binary run fpm install

Only a Fortran compiler is required to build projects, and git for git dependencies. fpm assumes gfortran unless told otherwise (see Selecting the compiler). Check the installation, and the tool versions it found, with:

fpm --version
Version:     0.13.0, alpha
Program:     fpm(1)
Description: A Fortran package manager and build system
Home Page:   https://github.com/fortran-lang/fpm
License:     MIT
OS Type:     Linux

The project layout and fpm new

fpm projects follow a layout that fpm discovers without configuration. The rules, from the manifest reference and the output of fpm new:

Path Meaning

fpm.toml

the manifest; the only required file is this one, containing at least name

src/

the library: every module and procedure file; compiled into lib<name>.a and visible to every other target and to packages that depend on this one

app/

executables: every file containing a program unit; the executable is named after the file (probe.f90 gives probe), except main.f90, which gives the package name

test/

tests: programs run by fpm test; they may use the library and any dev-dependency; files without a program in this directory (such as a test module) are support code for them

example/

examples: programs run by fpm run --example

include/

files for the Fortran include statement and the preprocessor #include; its presence also makes fpm generate the library target

build/

everything fpm generates (objects, modules, archives, executables, fetched dependencies); safe to delete

The discovery is recursive: app/tools/probe.f90 is found as well. Which directories exist decides which targets exist; an explicit [[executable]], [[test]] or [[example]] table in the manifest adds or renames targets, and auto-executables, auto-tests and auto-examples can switch the discovery off (see The manifest fpm.toml).

fpm new NAME creates the directory, runs git init in it and writes sample files. The name must be ASCII letters, digits, underscores or hyphens, at most 63 characters, and start with a letter:

fpm new first_steps
cd first_steps
find . -path ./.git -prune -o -type f -print | sort
cat fpm.toml
./README.md
./app/main.f90
./fpm.toml
./src/first_steps.f90
./test/check.f90
name = "first_steps"
version = "0.1.0"
license = "license"
author = "Jane Doe"
maintainer = "jane.doe@example.com"
copyright = "Copyright 2026, Jane Doe"

The author fields are placeholders (fpm fills them from the Git identity when one is configured; the container had none), and the generated manifest has no [build] or [install] table, because the defaults already do what is wanted. The generated src/first_steps.f90 is a module with a say_hello subroutine, app/main.f90 uses it, and test/check.f90 prints a placeholder message. Building, running and testing it:

fpm build
fpm run
fpm test
[  0%]                first_steps.f90
[ 25%]                first_steps.f90  done.
[ 25%]               libfirst_steps.a
[ 50%]               libfirst_steps.a  done.
[ 50%]                       main.f90
[ 75%]                       main.f90  done.
[ 75%]                    first_steps
[100%]                    first_steps  done.
[100%] Project compiled successfully.
Project is up to date
 Hello, first_steps!
[  0%]                      check.f90
[ 50%]                      check.f90  done.
[ 50%]                          check
[100%]                          check  done.
[100%] Project compiled successfully.
 Put some tests in here!

fpm run and fpm test first make sure the targets are built, which is why fpm run reports Project is up to date after the explicit build. The options of fpm new choose which directories are created:

Option Effect

--lib or --src

only src/ and a placeholder module (the result of fpm new libonly --lib is README.md, fpm.toml and src/libonly.f90)

--app

only app/ and a main program

--test

only test/ and a placeholder test program

--example

only example/ and a placeholder; with none of these options the default is --lib --app --test

--full

an extensively annotated fpm.toml that documents every key, and example/demo.f90, with explicit [[executable]], [[test]] and [[example]] tables

--bare

only fpm.toml and README.md, no directories and no Fortran

--backfill

allow an existing directory and create only the files that are missing

A name that fpm cannot accept is rejected by fpm new with the rule above and exit status 4 (STOP 4). The --full manifest is the best offline reference for the format: it documents multi-level library sources (a file src/a/b/c/d.f90 should define the module a_b_c_d, a convention fpm asks for but does not enforce), library types and every dependency form.

The commands

fpm has ten subcommands, listed by fpm --list or fpm help. Everything is built into build/, which can always be removed and rebuilt (a rebuild from scratch needs the network again when git dependencies are used).

Command What it does

fpm new

create a package directory with sample files (above)

fpm build

fetch dependencies, scan the sources, compile and link everything the targets need

fpm run

build and run the executables in app/ (or --example programs from example/); without a name it runs all of them

fpm test

build and run the programs in test/; a test fails when its program exits with a nonzero status

fpm install

install executables, and with [install] library = true also the library archive and the .mod files

fpm clean

delete the build directories, with a confirmation prompt unless --skip or --all is given

fpm update

re-resolve and re-fetch dependencies

fpm publish

upload the package to the registry

fpm list

a one-line description of every subcommand

fpm help

the manual page of a subcommand, for example fpm help runner for the --runner option

The options shared by build, run, test and install, from fpm build --help and the sibling help pages, are:

Option Meaning

--profile PROF

a built-in compilation profile (release or debug) or a profile defined in the manifest (see Features and profiles)

--features LIST

comma-separated features from the manifest; cannot be combined with --profile

--flag FFLAGS, --c-flag, --cxx-flag, --link-flag

extra Fortran, C, C++ and linker flags

--compiler NAME, --c-compiler, --cxx-compiler, --archiver

choose the tools

--build-dir DIR

use another build directory (default build)

--no-prune

do not drop unused module dependencies (see below)

--list

list targets without building or running them; for build it downloads dependencies and writes compile_commands.json without compiling

--verbose

show the commands fpm runs

--config-file PATH

another global configuration file

-- ARGS

on run and test, the arguments passed to the program

--runner CMD, --runner-args ARGS

prefix the program with a command such as a debugger, valgrind or mpiexec (see Running with a runner)

Two further options belong to individual commands: fpm build --tests builds all tests even when nothing needs them, and fpm install --no-rebuild installs what is already built. -C DIR (or --directory DIR) changes to another directory first. It is given after the subcommand, fpm run -C /work/first_steps; written before the subcommand in the container it was rejected, and the command printed the help text instead. Long option lists can be kept in a response file: fpm @release reads release.rsp from the current directory, each line written as options followed by one argument.

options run
options --profile release

Building the heatflow project

The heatflow project from the earlier pages is an fpm project with a library, one application, one example and one test program. The manifest is the one from Getting Started with auto-examples and an explicit [[test]] table added:

name = "heatflow"
version = "0.1.0"
license = "MIT"
author = "Heatflow authors"
description = "2D heat diffusion on a rectangular plate"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = true

[fortran]
implicit-typing = false
implicit-external = false
source-form = "free"

[dev-dependencies]
test-drive = { git = "https://github.com/fortran-lang/test-drive", tag = "v0.6.1" }

[[test]]
name = "check"
source-dir = "test"
main = "check.f90"

The first build also fetches the dev-dependency. The compile order is computed from the use and submodule statements: the submodules heatflow_ftcs and heatflow_jacobi are compiled after their parent module heatflow_solver, although fpm starts the files in an order that respects those dependencies.

fpm build
From https://github.com/fortran-lang/test-drive
 * tag               v0.6.1     -> FETCH_HEAD
[  0%]          heatflow_boundary.f90
[  6%]          heatflow_boundary.f90  done.
[  6%]             heatflow_kinds.f90
[ 12%]             heatflow_kinds.f90  done.
[ 12%]         heatflow_constants.f90
[ 18%]         heatflow_constants.f90  done.
[ 18%]              heatflow_grid.f90
[ 25%]              heatflow_grid.f90  done.
[ 25%]           heatflow_kernels.f90
[ 31%]           heatflow_kernels.f90  done.
[ 31%]              heatflow_capi.f90
[ 37%]              heatflow_capi.f90  done.
[ 37%]            heatflow_config.f90
[ 43%]            heatflow_config.f90  done.
[ 43%]                heatflow_io.f90
[ 50%]                heatflow_io.f90  done.
[ 50%]            heatflow_solver.f90
[ 56%]            heatflow_solver.f90  done.
[ 56%]      do_concurrent_stencil.f90
[ 62%]      do_concurrent_stencil.f90  done.
[ 62%]              heatflow_ftcs.f90
[ 68%]              heatflow_ftcs.f90  done.
[ 68%]            heatflow_jacobi.f90
[ 75%]            heatflow_jacobi.f90  done.
[ 75%]                       main.f90
[ 81%]                       main.f90  done.
[ 81%]                  libheatflow.a
[ 87%]                  libheatflow.a  done.
[ 87%]                       heatflow
[ 93%]                       heatflow  done.
[ 93%]          do_concurrent_stencil
[100%]          do_concurrent_stencil  done.
[100%] Project compiled successfully.

The git init messages that precede the From line in the real output are omitted. fpm test, fpm run and fpm run --example then give:

fpm test
fpm run
fpm run --example do_concurrent_stencil
...
# Testing: grid
  Starting spacing ... (1/2)
       ... spacing [PASSED]
  Starting dirichlet ... (2/2)
       ... dirichlet [PASSED]
...
steps=2000 residual= 1.38E-04
max temperature=   0.139
...
residual= 2.514E-03 max=   0.499

The lines marked ... stand for fpm’s progress messages. Listing the targets before running them:

fpm run --list
fpm run --example --list
fpm test --list
 Matched names:
heatflow
 Matched names:
do_concurrent_stencil
 Matched names:
check

Selecting targets and passing arguments

fpm run and fpm test take target names, with simple globbing: ? matches one character and * any string, quoted against the shell. --all is an alias for --target '*', and in fpm 0.13.0 a bare fpm run also runs every executable (the help text says a list is printed when several candidates exist and no name is given, but the version tested ran them all). In a project with app/main.f90, app/tools/probe.f90 and two test programs the commands behave like this:

fpm run --list
fpm test --list
fpm run --all
fpm run "pro*"
 Matched names:
first_steps  probe
 Matched names:
check  more
 Hello, first_steps!
 probe
 probe

Everything after -- goes to the program. The heatflow application takes the name of a namelist file, and its error stop 2 for a missing file is reported by fpm as a failed execution:

fpm run -- heatflow.nml
fpm run -- missing.nml
steps=2000 residual= 1.38E-04
max temperature=   0.139
cannot read configuration: Cannot open file 'missing.nml': No such file or directory
ERROR STOP 2

Error termination. Backtrace:
#0  0xffffae8f196f in ???
...
<ERROR> Execution for object " heatflow " returned exit code  2
<ERROR> *cmd_run*:stopping due to failed executions
STOP 2

The same applies to fpm test: a test program signals failure with a nonzero exit status, for example through error stop, and fpm test itself then exits with status 1 (checked with a one-line test program that executes error stop "boom"). That is the contract that makes fpm usable in continuous integration; the statements are described in Error Handling and Program Termination.

Running with a runner

--runner CMD puts a command in front of the program path. It is available for run and test, which makes it the hook for debuggers, valgrind, mpiexec and tracing tools. Without a value the runner is echo, which prints the paths of the executables instead of running them. --runner-args passes arguments to the runner itself, as opposed to -- ARGS, which goes to the program. A value that begins with a dash is read by fpm’s argument parser as options, so write --runner-args=" -np 4" with an equals sign and a leading space:

fpm test --runner "valgrind -q"
fpm run --runner echo
fpm run --runner "mpiexec" --runner-args=" -n 3"

The first command ran the test program under valgrind and printed the same two PASSED lines, the second printed build/gfortran_DF6D0BB80A7053CC/app/heatflow, and the third started the MPI program of the mpi metapackage example below on three ranks. With the value -np 4 and no equals sign the container’s fpm answered UNKNOWN COMPOUND SHORT KEYWORD:n in -np 4 followed by a table of its options.

Incremental builds and the recompilation cascade

fpm decides whether a file must be rebuilt from the content of the source, not from its timestamp, and it rebuilds every file that depends on a changed module. Touching a file changes nothing:

touch src/heatflow_kinds.f90
fpm build
Project is up to date

A real change in a deep module cascades through everything that uses it, directly or not (here a comment was appended to heatflow_kinds.f90, which every other module uses; only heatflow_boundary, which does not use it, was left alone):

[  6%]             heatflow_kinds.f90  done.
[ 13%]         heatflow_constants.f90  done.
[ 20%]              heatflow_grid.f90  done.
[ 26%]           heatflow_kernels.f90  done.
[ 33%]              heatflow_capi.f90  done.
[ 40%]            heatflow_config.f90  done.
[ 46%]                heatflow_io.f90  done.
[ 53%]            heatflow_solver.f90  done.
[ 60%]      do_concurrent_stencil.f90  done.
[ 66%]              heatflow_ftcs.f90  done.
[ 73%]            heatflow_jacobi.f90  done.
[ 80%]                       main.f90  done.
[ 86%]                  libheatflow.a  done.
[ 93%]                       heatflow  done.
[100%]          do_concurrent_stencil  done.
[100%] Project compiled successfully.

The same comment appended to a submodule, heatflow_jacobi.f90, rebuilds only that file, relinks the archive and the application (do_concurrent_stencil does not use the solver and is not relinked):

[ 25%]            heatflow_jacobi.f90  done.
[ 50%]                  libheatflow.a  done.
[ 75%]                       heatflow  done.
[100%] Project compiled successfully.

That measurement is the whole argument for splitting implementations into submodules, discussed in Modules and Submodules: fpm cannot tell that a comment does not change a module’s interface, so a changed module file recompiles its users, but a changed submodule has no users.

The build directory

Objects, .mod files, archives and executables live in subdirectories of build/ named after the compiler and a hash (build/gfortran_DD609D2CDA6636FC). Builds with different compilers, profiles or flags normally get different directories and do not overwrite each other, and a cache of the resolved dependencies is kept in build/cache.toml. With --build-dir DIR or the environment variable FPM_BUILD_DIR the whole directory moves (fpm build --build-dir /tmp/mybuild produced cache.toml, compile_commands.json, dependencies and the hashed directories there).

The module files are written to the hashed directory itself and the objects to a subdirectory named after the package; the directory is passed with -J and -I, which is the "module directory" of the compiler options in Compilers and Compiler Options. To see exactly what fpm runs, use --verbose, or read build/compile_commands.json (see compile_commands.json and editors). For heatflow_grid.f90 with --profile release:

gfortran -c ././src/heatflow_grid.f90 -O3 -Wimplicit-interface -fPIC -fmax-errors=1 -funroll-loops -fcoarray=single -fimplicit-none -Werror=implicit-interface -ffree-form -I././include -J build/gfortran_C40FC9334A6DC5D6 -Ibuild/gfortran_C40FC9334A6DC5D6 -o build/gfortran_C40FC9334A6DC5D6/heatflow/src_heatflow_grid.f90.o

The last four options before -I././include come from the manifest, not from the profile: -fimplicit-none and -Werror=implicit-interface follow from implicit-typing = false and implicit-external = false (the defaults), and -ffree-form from source-form = "free". With implicit-typing = true and implicit-external = true and source-form = "default", the same file was compiled with none of the three. A missing implicit none is therefore caught in fpm projects by the flag (see The fortran table).

Pruning ("tree-shaking"): in the default monolithic library mode, the archive contains only the objects that the applications, tests and examples actually need; the others are still compiled. --no-prune keeps all of them.

Profiles, flags and environment variables

What a build passes by default

A plain fpm build in fpm 0.13.0 passed no optimisation, warning or run-time checking options at all, whatever the documentation suggests about an "implicit debug build": the compile line for first_steps.f90 was gfortran -c ././src/first_steps.f90 -fimplicit-none -Werror=implicit-interface -ffree-form -J ... -I... -o ..., and fpm build --verbose printed an empty COMPILER OPTIONS line. The profile has to be named. With the GNU compiler the two built-in profiles gave:

Profile gfortran options (from fpm build --profile NAME --verbose)

debug

-Wall -Wextra -Wimplicit-interface -Wno-external-argument-mismatch -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single

release

-O3 -Wimplicit-interface -fPIC -fmax-errors=1 -funroll-loops -fcoarray=single

The exact lists belong to fpm and may change between versions; read them with --verbose. -fcoarray=single is one of the three values gfortran accepts (none, single, lib) and compiles coarray code for a single image, see Coarrays, Images and Synchronization. The C and C++ options are derived in the same way for projects with C sources (-Wall -Wextra -fPIC -fmax-errors=1 -g for debug).

A consequence worth knowing: because the debug profile includes -fPIC and a plain build does not, a plain build cannot link a shared library (see Library types and installation).

Adding flags with --flag and the environment

--flag FFLAGS is added to the options of the named profile; without a profile it is the only option fpm passes, replacing the defaults. It has the strongest use in continuous integration, to impose strict standard checking whatever the profile does:

fpm run --profile release --flag "-march=native"
fpm test --flag "-std=f2023 -Wall -Wextra -pedantic -fcheck=all"

For --profile release --flag "-march=native" the compile line becomes gfortran -c ././src/heatflow_grid.f90 -march=native -O3 -Wimplicit-interface -fPIC -fmax-errors=1 -funroll-loops ..., with the user flag first. Every setting has an environment variable for use in scripts and CI; the command-line option wins when both are set.

Variable Overridden by Meaning

FPM_FC

--compiler

the Fortran compiler (default gfortran)

FPM_FFLAGS

--flag

Fortran compiler flags

FPM_CC, FPM_CFLAGS

--c-compiler, --c-flag

the C compiler and its flags

FPM_CXX, FPM_CXXFLAGS

--cxx-compiler, --cxx-flag

the C++ compiler and its flags

FPM_AR

--archiver

the archiver

FPM_LDFLAGS

--link-flag

extra linker arguments

FPM_BUILD_DIR

--build-dir

the build directory

For MPI, fpm additionally reads MPIFC, MPIF90, MPIF77, MPICC and MPICXX (and the I_MPI_* variables for Intel MPI) to find compiler wrappers, see Metapackages.

Selecting the compiler

--compiler NAME or FPM_FC picks the compiler by executable name, and fpm identifies the family from the name. Building the whole heatflow project with Flang worked unchanged; the compiler name only has to be in the PATH:

fpm run --compiler flang-23
fpm test --compiler flang-23
...
steps=2000 residual= 1.38E-04
max temperature=   0.139
...
# Testing: grid
  Starting spacing ... (1/2)
       ... spacing [PASSED]
  Starting dirichlet ... (2/2)
       ... dirichlet [PASSED]

The results are identical to the gfortran build, and the build directories are named flang-23_... next to the gfortran_... ones. Two caveats were measured. First, with the versioned name flang-23, or even with a symbolic link named flang, --profile debug and --profile release passed no options at all (COMPILER OPTIONS was empty), so on Flang use --flag, or a feature with a flang section (see Features and profiles). Second, the openmp metapackage also probes the C compiler; in the container, which has Flang but no clang, fpm run --compiler flang stopped with failed command clang -c ... -fopenmp, so OpenMP with Flang is compiled with --flag "-fopenmp" instead, see OpenMP and OpenACC. Flang rejects -std=f2023: it prints error: Only -std=f2018 is allowed currently.

The compiler names fpm knows (for the per-compiler sections of the manifest) are: gfortran, ifort, ifx, nvfortran, nagfor, flang, amdflang, lfortran, f95, ftn95, caf, pgfortran, xlf90, crayftn and lfc. Since 0.13.0 the name flang means LLVM Flang, and amdflang is supported. No other compiler (Intel ifx, NAG, Cray, NVHPC) was available for this page.

The manifest fpm.toml

The manifest is a TOML file. Its top level has the package metadata and the tables below; fpm new --full writes a commented sample of every one.

Package metadata

Key Meaning

name

required; identifies the package, and is the default name of the library and of the executable built from app/main.f90; letters, digits and underscores, hyphens treated as underscores

version

a string, preferably a Semantic Versioning number; it may instead name a file relative to the project root that contains the version

license

an SPDX identifier such as MIT, LGPL-3.0-or-later or Apache-2.0 OR MIT

maintainer, author, copyright

free text; maintainer is an e-mail address by convention

description, categories, keywords, homepage

plain-text summary (no markup), a list of categories (one is preferred), a list of keywords and a URL

Targets: library, executable, test and example

The library target is generated when src/ or include/ exists. Its [library] table moves them:

[library]
source-dir = "lib"
include-dir = ["include", "third_party/include"]
type = "monolithic"

source-dir is relative to the project root, with / as the separator on all platforms. include-dir takes one directory or a list and is added to the include path of the compiler for the package and its dependents (it does not allow pre-built .mod files). type is covered in Library types and installation.

Executables, tests and examples are alike. Each is declared with an array-of-tables entry; name is required, and source-dir (default app, test or example) and main (the file with the program) are optional:

[[executable]]
name = "hot-spot"
source-dir = "app"
main = "hot_spot.f90"

[[test]]
name = "tester"
link = ["lapack", "blas"]
[test.dependencies]
test-drive = { git = "https://github.com/fortran-lang/test-drive", tag = "v0.6.1" }

[[example]]
name = "demo-app"
source-dir = "demo"
main = "program.f90"

The last two entries show the syntax of the reference and were not built here. Each entry may also declare its own link libraries and its own dependencies, which are used only by that target and are not exported. For many targets an inline array is shorter: executable = [ { name = "a-prog" }, { name = "app-tool", source-dir = "tool" } ], which must come before the first [table] header of the file.

To see an explicit executable at work, here is a second fpm project, heatflow-cli, that depends on heatflow by path (the dependency forms are in Dependencies) and has its own application. app/hot_spot.f90:

program hot_spot
  use heatflow_kinds, only: dp
  use heatflow_grid, only: grid_t, make_grid
  implicit none
  type(grid_t) :: g

  g = make_grid(11, 11, 1.0_dp, 1.0_dp)
  call g%set_hot_spot(100.0_dp)
  print '(a, f8.3)', 'hottest cell: ', g%max_temperature()
end program hot_spot

and its manifest:

name = "heatflow-cli"
version = "0.1.0"
license = "MIT"

[dependencies]
heatflow = { path = "../heatflow" }

[[executable]]
name = "hot-spot"
source-dir = "app"
main = "hot_spot.f90"
fpm run
...
[ 75%]              libheatflow-cli.a
[ 87%]              libheatflow-cli.a  done.
[ 87%]                       hot-spot
[100%] hot-spot  done.
[100%] Project compiled successfully.
hottest cell:  100.000

(The executable is hot-spot, named in the manifest, not after the file.) The program compiles without warnings under the strict gfortran options, passed with --flag, and builds and runs identically with --compiler flang. Note that the dev-dependency test-drive of heatflow was not fetched for this consumer: build/dependencies stayed empty, because development dependencies are not exported.

The build table

Key Meaning

auto-executables, auto-tests, auto-examples

true by default; set to false to stop the recursive discovery in app/, test/ and example/ and use only the explicit tables

link

native libraries to link, as one string or a list; the list is exported to dependents; the order matters (a library before the libraries it depends on)

external-modules

names of modules that come from outside the package (for example netcdf); fpm does not look for their files

module-naming

true or a prefix string: require the module-name convention (see Module naming and the registry); default false

Link order is the classic trap. LAPACK depends on BLAS, so link = ["lapack", "blas"] is the correct order. With the reference BLAS and LAPACK shared libraries of the container both orders happened to link and print ddot = 14.0, because shared libraries record their own dependencies; with static libraries the wrong order fails. A library that does not exist is a linker error, not an fpm error:

/usr/bin/ld: cannot find -lnosuchlib: No such file or directory
collect2: error: ld returned 1 exit status
<ERROR> Compilation failed for object " linkdemo "
<ERROR> stopping due to failed compilation

external-modules is for modules that fpm cannot find among the sources. fpm must not report them as missing, but it does not locate their files or their libraries: the include paths and libraries are the user’s responsibility. Compare three cases for a program that says use omp_lib. A module that is missing everywhere is an fpm error (the program here uses a module named nosuch_module):

<ERROR> *cmd_build* Target error: Unable to find source for module dependency: "nosuch_module" used by "app/main.f90"

omp_lib is a compiler-provided module, so gfortran finds it by itself, the fpm scan lets it through, and the failure comes at link time because nothing links OpenMP:

/usr/bin/ld: build/gfortran_46B186824249A86E/extmod/app_main.f90.o: in function `MAIN__':
main.f90:(.text+0x64): undefined reference to `omp_get_max_threads_'
collect2: error: ld returned 1 exit status

With external-modules = "omp_lib" in [build] and fpm run --flag "-fopenmp" it builds and prints OpenMP threads available: 10. The openmp metapackage (below) does all of this without the manual steps.

The fortran table

[fortran]
implicit-typing = false
implicit-external = false
source-form = "free"

implicit-typing (default false) and implicit-external (default false) control whether fpm omits -fimplicit-none and -Werror=implicit-interface; source-form is "free" (default), "fixed" or "default", which lets the compiler decide from the file extension. The defaults mean that modern code gets the safe flags automatically and legacy code must opt out with implicit-typing = true and implicit-external = true, which is also how a fixed-form legacy package is described (with fpm 0.13.0 such a package also needed an explicit [[executable]] entry with main = "main.f", because app/main.f was not discovered automatically), see Legacy Features and Modernization. Writing implicit none in the source is still the recommended style, because it does not depend on the build tool.

The install table and the extra table

[install] library = true makes fpm install also install the library archive and the module files; without it only executables are installed. module-dir (since 0.13.0) changes the subdirectory of the prefix for the .mod files (default include). See Library types and installation.

Third-party tools keep their own configuration in [extra], which fpm itself never evaluates; the convention is one subtable per tool (extra.fpm.<plugin> for fpm plugins), keys in lowercase with dashes.

The preprocess table

[preprocess.cpp] enables the C preprocessor for the package, with the keys suffixes (file suffixes to preprocess), directories (limit it to directories) and macros (a list of NAME or NAME=VALUE). See Preprocessing and conditional compilation.

Dependencies

Dependencies are declared in [dependencies] of the manifest (exported to packages that use this one), in [dev-dependencies] (available to tests and examples of this package, not exported), or inside an individual target table. Whatever the source, every dependency is an fpm package: it has its own fpm.toml, or, for a git repository without one, the defaults.

Path dependencies

A path is relative to the fpm.toml that contains it and uses / on all platforms. It is the form for several packages in one repository, and for the heatflow-cli example above. After building, build/cache.toml records what fpm resolved:

[heatflow-cli]
version = "0.1.0"
proj-dir = "."
[heatflow]
version = "0.1.0"
proj-dir = "../heatflow"

Git dependencies

A git dependency names a repository and at most one of branch, tag or rev. Without one, the default branch is used.

[dependencies]
test-drive = { git = "https://github.com/fortran-lang/test-drive", tag = "v0.6.1" }
# or: branch = "main"
# or: rev = "c506771aefd594e7e372240a8027b3fd06d61264"

All three were fetched and built, and build/cache.toml recorded obj and the full commit that obj resolved to (rev = "c506771aefd594e7e372240a8027b3fd06d61264" for the tag v0.6.1, and for branch = "main" the commit of the main branch on the day, which changes). The errors are:

Manifest Result in fpm 0.13.0

tag = "v0.6.1", branch = "main"

<ERROR> *cmd_build* Package error: Dependency 'test-drive' can only have one of branch, rev or tag present

tag = "v9.9.9" (no such tag)

fatal: couldn't find remote ref v9.9.9 and <ERROR> *cmd_build* Model error: Error while fetching git repository for remote dependency

rev = "9c3401e" (abbreviated hash)

fatal: couldn't find remote ref 9c3401e and the same fetch error; write the full 40-character hash

Pin to a tag or a full rev in anything that must be reproducible; a branch (or no reference) moves under you. A dependency is cloned into build/dependencies/<name>, built with the same compiler and flags as the main package, and reused until fpm update or a changed reference. fpm update takes dependency names, --fetch-only to fetch without updating existing projects, --clean to ignore the cache, and --dump FILE to write the resolved dependency tree (TOML, or JSON for a .json name). Dependencies of dependencies are resolved recursively: the stdlib example below brought in test-drive version 0.4.0, which the stdlib package depends on, next to stdlib itself.

A dependency can be given more control with a dependency-specific preprocessor table, since fpm 0.9.1: fftpack = { git = "https://github.com/fortran-lang/fftpack.git", preprocess.cpp.macros = ["REAL32"] }. fpm does not check for collisions with the dependency’s own macros.

Registry dependencies

A package from the registry names a namespace and, optionally, a version:

[dependencies]
example-package.namespace = "example-namespace"
example-package.v = "1.0.0"

Without v the newest version is used. The registry settings live in the global configuration file, ~/.local/share/fpm/config.toml on Unix-like systems and %APPDATA%\local\fpm\config.toml on Windows (or the file given with --config-file): [registry] url selects another registry that implements the same API, [registry] path a local directory, and cache_path moves the cache of downloaded packages (default ~/.local/share/fpm/dependencies). A local registry is laid out as <registry>/<namespace>/<package>/<version>/fpm.toml, and fpm then neither downloads nor uses the cache.

This was not usable on the day of the check. The URL that fpm 0.13.0 requested for a registry dependency, https://fpm-registry.vercel.app/packages/toml-f/jonquil/0.3.0, answered HTTP 500 (FUNCTION_INVOCATION_FAILED), and fpm reported:

 Downloading 'https://fpm-registry.vercel.app/packages/toml-f/jonquil/0.3.0' -> '/tmp/filelW9kr2'
<ERROR> *cmd_build* Model error: error: Invalid expression for value
 --> /tmp/filelW9kr2:1:1
  |
1 | A server error has occurred
  | ^ unexpected invalid sequence
  |

The official documentation itself says the online registry is under active development. Today git dependencies are what the ecosystem uses, and the curated package index at https://fortran-lang.org/packages/ is a catalogue of repositories, not a registry that fpm queries.

Source: fpm: Registry

Development dependencies and target-specific dependencies

The heatflow manifest keeps test-drive in [dev-dependencies]: it is fetched and built for fpm test, but a package that depends on heatflow does not get it. The pin is a tag because a test framework must not change under the project. A dependency needed by just one program, such as a command-line parser used only by the application, belongs in that target’s table, so the library keeps its dependency list short:

[[executable]]
name = "demo"
[executable.dependencies]
M_CLI2.git = "https://github.com/urbanjost/M_CLI2"

Metapackages

Many real programs need dependencies that are not Fortran packages in a git repository: OpenMP, MPI, BLAS, HDF5. A metapackage is a dependency whose name fpm recognises; fpm then finds the library on the system, adds the compiler and linker flags and the include paths, and (for MPI) a runner. It is enabled by name with the wildcard version "*"; metapackages are experimental in the documentation, and they exist since fpm 0.8.3.

[dependencies]
openmp = "*"
Metapackage What it does Checked here

stdlib

adds the fortran-lang standard library as a git dependency; with blas also enabled, stdlib is configured to link the external BLAS/LAPACK, defining the macros STDLIB_EXTERNAL_BLAS and STDLIB_EXTERNAL_LAPACK (since 0.12.0)

yes, below

minpack

adds the modernised fortran-lang MINPACK (release v2.0.0-rc1 at the time of the documentation) as a git dependency

no

openmp

adds the compiler’s OpenMP flags to compiling and linking

yes, with gfortran

mpi

finds MPI compiler wrappers (mpifort, mpif90, mpif77, mpicc, mpicxx), matches them with the fpm compiler, and takes flags and runner (mpiexec or mpirun) from them; Intel MPI, MS-MPI and SLURM srun are supported

yes, with MPICH

hdf5

finds HDF5 through pkg-config, linking the default and the high-level interfaces, for Fortran, C and C++

no

netcdf

finds NetCDF through pkg-config and links the Fortran and C libraries; exposes the modules netcdf, netcdf_f03 and netcdf4_f03 (since 0.12.0)

no

blas

finds BLAS in the order Apple Accelerate, Intel MKL, OpenBLAS, reference BLAS (since 0.12.0)

yes, OpenBLAS

Since 0.13.0, a metapackage used by a dependency is propagated to the packages that depend on it, so flags and libraries stay consistent along the dependency tree. Each checked metapackage was exercised with a three-line manifest and a small program.

OpenMP. dependencies.openmp = "*" is equivalent to the table form. With this program:

program main
  use omp_lib, only: omp_get_max_threads
  implicit none

  print '(a, i0)', 'OpenMP threads available: ', omp_get_max_threads()
end program main
OMP_NUM_THREADS=4 fpm run
OpenMP threads available: 4

The program also compiled warning-free under the strict gfortran options. fpm added -fopenmp to the compile line (twice, see the note on repeated flags in Features and profiles) and to the link line. With Flang see the caveat in Selecting the compiler.

BLAS. The metapackage found OpenBLAS in the container. The program calls the BLAS function ddot through an explicit interface (see BLAS, LAPACK and Numerical Libraries):

program main
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  interface
    function ddot(n, x, incx, y, incy) result(r)
      import :: real64
      integer, intent(in) :: n, incx, incy
      real(real64), intent(in) :: x(*), y(*)
      real(real64) :: r
    end function ddot
  end interface
  real(real64) :: a(3) = [1.0_real64, 2.0_real64, 3.0_real64]

  print '(a, f5.1)', 'ddot = ', ddot(3, a, 1, a, 1)
end program main
ddot =  14.0

It built and ran with both gfortran and Flang. (This is 1 + 4 + 9.)

stdlib. dependencies.stdlib = "*" downloads the standard library as a git dependency:

program main
  use stdlib_kinds, only: dp
  use stdlib_strings, only: to_string
  implicit none

  print '(a)', 'stdlib dp = '//to_string(dp)//', 2 + 2 = '//to_string(2 + 2)
end program main
stdlib dp = 8, 2 + 2 = 4

build/cache.toml shows what the metapackage resolved to: the fortran-lang/stdlib repository, its stdlib-fpm branch (the branch that carries the fpm packaging), version 0.8.1, and, as stdlib’s own dependency, test-drive 0.4.0. The first build compiles the whole library; the stdlib modules are described on Fortran stdlib.

MPI. The metapackage needs two settings in the main package that are not obvious. Without them fpm stops before compiling:

<ERROR> *cmd_run* Model error: metapackage fortran error: metapackage does require implicit-external, main package does not

and, once implicit-external = true is set, the same message about implicit-typing. Both must be true for the MPICH wrappers of the container, which means fpm omits -fimplicit-none for the package; the sources keep implicit none.

name = "meta-mpi"
version = "0.1.0"

[dependencies]
mpi = "*"

[fortran]
implicit-typing = true
implicit-external = true
program main
  use mpi_f08
  implicit none
  integer :: rank, nranks

  call MPI_Init()
  call MPI_Comm_rank(MPI_COMM_WORLD, rank)
  call MPI_Comm_size(MPI_COMM_WORLD, nranks)
  if (rank == 0) print '(a, i0, a)', 'running on ', nranks, ' ranks'
  call MPI_Finalize()
end program main
fpm run
fpm run --runner "mpiexec -n 4"
running on 1 ranks
running on 4 ranks

With no runner option the program ran on a single rank (fpm looked up mpiexec; the manual says the default runner uses the default number of processes of the node). The mpi_f08 binding and mpiexec are the subject of MPI with mpi_f08. The program also compiled under the strict gfortran options.

HDF5, NetCDF and MINPACK use the same one-line form (dependencies.hdf5 = "*", dependencies.netcdf = "*", dependencies.minpack = "*") and were not run, since the libraries are not in the container. HDF5 needs pkg-config on the system, and codes using an HDF5 built with the oneAPI compilers must not use the -standard-semantics flag, which changes the module-name mangling and makes the HDF5 modules unavailable.

Features and profiles

Features and profiles arrived in fpm 0.13.0. A feature is a named collection of manifest properties that is switched on by name, by compiler or by operating system. A profile is a named list of features. They replace the practice of keeping flag lists in shell scripts, and they are how a single manifest can say "strict standard checking with gfortran, -pedantic only with Flang, OpenMP in production".

Defining features

Features live in the [features] table, written with dotted keys:

feature.property = value
feature.compiler.property = value
feature.os.property = value
feature.compiler.os.property = value

A feature can set compiler flags (flags, c-flags, cxx-flags, link-time-flags), build, Fortran and library settings (build.link, fortran.source-form, library.type), dependencies, dev-dependencies, extra executable, test and example targets, preprocess macros, and install and description entries. Compiler names are the list in Selecting the compiler; operating systems are linux, macos, windows, cygwin, solaris, freebsd and openbsd.

When several variants of a feature match, fpm merges them in this order: the base properties, then the compiler’s, then the operating system’s, then the combined compiler-and-system’s. Flags (flags, c-flags, cxx-flags, link-time-flags) are concatenated with spaces, the lists (dependencies, dev-dependencies, executable, test, example, preprocess) are appended, and the tables build, install, fortran and library are exclusive: defining the same one in two variants of the same feature is an error.

Profiles

Profiles group features in [profiles]. debug and release exist as built-in profiles with the flags of the table above; if the manifest defines debug or release, its definition replaces the built-in one completely. A profile named default holds features that are applied together with debug and release, but not with a custom profile or with --features.

Here is a manifest for heatflow, added to the one shown earlier, with a ci profile for strict checking and a production profile for speed with OpenMP. The flang sections show how one feature serves two compilers:

[features]
strict.gfortran.flags = "-std=f2023 -Wall -Wextra -pedantic"
strict.flang.flags = "-pedantic"
checks.gfortran.flags = "-g -fcheck=all -fbacktrace"
fast.flags = "-O3"
fast.gfortran.flags = "-march=native -funroll-loops"
openmp.dependencies.openmp = "*"

[profiles]
ci = ["strict", "checks"]
production = ["fast", "openmp"]

Selecting them:

fpm build --profile ci --verbose
fpm test --profile ci
fpm run --profile production
fpm build --features strict,checks --verbose

The COMPILER OPTIONS line of the first and the last build was identical, and the tests passed under the ci profile with the same output as before:

 <INFO> COMPILER OPTIONS:    -std=f2023 -Wall -Wextra -pedantic -g -fcheck=all -fbacktrace

For the production profile the compile line of heatflow_grid.f90 was gfortran -c ././src/heatflow_grid.f90 -O3 -march=native -funroll-loops -O3 -march=native -funroll-loops -fopenmp -O3 -march=native -funroll-loops -O3 -march=native -funroll-loops -fopenmp -fimplicit-none ...: the executable linked libgomp.so.1 and gave the same results as before, but the -O3 -march=native -funroll-loops group appeared four times and -fopenmp twice, an inefficiency (not an error) of fpm 0.13.0 that is worth knowing about when reading --verbose output. The same manifest with --compiler flang --features strict passed -pedantic to Flang and built heatflow successfully.

Mutually exclusive options and mistakes

--profile and --features cannot be used together, and an unknown profile is an error:

<ERROR> Error: --profile and --features cannot be used together
<ERROR> *cmd_build* Model error: Cannot find profile nope in package heatflow

Name a profile that contains the features you want (--profile ci), or list all features (--features release,mpi). The --help text of fpm build describes the first form as "feature profiles group multiple features together". A dependency can be built with its own features, my-lib = { git = "...", features = ["mpi"] }, or profile, my-lib = { git = "...", profile = "parallel-mpi" }, but not both.

Preprocessing and conditional compilation

fpm preprocesses with the C preprocessor, which the compilers run with -cpp (gfortran) or by file extension (.F90, the capital F by convention means "preprocess"). [preprocess.cpp] turns it on, with the optional keys suffixes, directories and macros. fpm passes the macros as -D options; a feature can contribute more. Here a feature selects the working precision of a small module:

name = "cppdemo"
version = "0.2.0"

[preprocess.cpp]
suffixes = ["F90"]

[features]
single.preprocess.cpp.macros = ["PRECISION=32"]
double.preprocess.cpp.macros = ["PRECISION=64"]

[profiles]
sp = ["single"]
dp = ["double"]
module working_precision
#if PRECISION == 32
  use, intrinsic :: iso_fortran_env, only: wp => real32
#else
  use, intrinsic :: iso_fortran_env, only: wp => real64
#endif
  implicit none
  private
  public :: wp
end module working_precision

with the program app/main.f90:

program main
  use working_precision, only: wp
  implicit none

  print '(a, i0, a, es12.5)', 'kind=', kind(1.0_wp), ' epsilon=', epsilon(1.0_wp)
end program main
fpm run
fpm run --profile sp
fpm run --profile dp
kind=8 epsilon= 2.22045E-16
kind=4 epsilon= 1.19209E-07
kind=8 epsilon= 2.22045E-16

Each was run in a clean build directory; gfortran and Flang 23 gave the same lines, with gfortran -c ... -cpp ... -DPRECISION=32 in the compile line for --profile sp. The VERSION={version} form from the manifest reference substitutes the manifest version (the compile line shows -DVERSION=0.2.0), but as an unquoted token: the C preprocessor does not substitute inside a Fortran string literal, and passing a quoted value through the shell did not work in the version tested, so use it for numeric tests only.

Conditional module use. Since 0.13.0, fpm evaluates #ifdef, #ifndef, #if defined(NAME), #if NAME == VALUE, #elif, #else and #endif against the manifest macros while it scans the sources, so a use mpi_f08 inside #ifdef USE_MPI is a dependency only when the macro is defined. A macro without a value is true for #ifdef; a macro with value 0 is false in #if; an undefined macro is false; names are case-sensitive. The compiler does the actual preprocessing, so the files need the right extension.

A stale-build hazard. Switching between profiles that differ only in preprocessor macros, back and forth, produced a wrong executable in fpm 0.13.0. In a clean tree, --profile sp, then --profile dp, then --profile sp again gave:

fpm run --profile sp
fpm run --profile dp
fpm run --profile sp
kind=4 epsilon= 1.19209E-07
kind=8 epsilon= 2.22045E-16
Project is up to date
kind=8 epsilon= 2.22045E-16

The third run claims the project is up to date and runs the executable of the dp build. fpm clean --apps followed by fpm run --profile sp printed kind=4 again, and so did fpm clean --skip (all build directories, keeping dependencies). Delete the build directory when you change macros by hand, and in CI build each variant in a clean tree or with its own --build-dir.

fypp. The documentation lists [preprocess.fypp] next to cpp, but fpm 0.13.0 answered a package with [preprocess.fypp] and a .fypp source with Warning: Preprocessor fypp is not supported; will ignore it and then Unable to find source for module dependency. Run fypp as a separate step and keep the generated .f90 or .F90 files under src/ (this is also the usual way the stdlib and other fypp-using projects are built).

Library types and installation

fpm install copies the executables to <prefix>/bin (the default prefix is $HOME/.local). With [install] library = true it also installs the library to <prefix>/lib and the module files to <prefix>/include:

fpm install --list
fpm install --profile release --prefix /tmp/inst
# total number of installable targets: 2
- build/gfortran_564D8A733B9263EA/heatflow/libheatflow.a
- build/gfortran_DF6D0BB80A7053CC/app/heatflow
...
/tmp/inst/bin/heatflow
/tmp/inst/include/heatflow_boundary.mod
/tmp/inst/include/heatflow_capi.mod
/tmp/inst/include/heatflow_config.mod
/tmp/inst/include/heatflow_constants.mod
/tmp/inst/include/heatflow_grid.mod
/tmp/inst/include/heatflow_io.mod
/tmp/inst/include/heatflow_kernels.mod
/tmp/inst/include/heatflow_kinds.mod
/tmp/inst/include/heatflow_solver.mod
/tmp/inst/lib/libheatflow.a

(The file list is the result of find /tmp/inst -type f | sort.) The submodule files heatflow_ftcs and heatflow_jacobi do not appear: only module files are installed, and a program that uses heatflow_solver needs only that .mod file. --bindir, --libdir, --includedir and --testdir change the subdirectories, --test installs the test programs too, and --no-rebuild installs what was built. With module-dir = "fortran/modules" in the [install] table and --prefix /tmp/i3, the module files went to /tmp/i3/fortran/modules/ and the archive to /tmp/i3/lib/. A separate program, c.f90, was compiled and linked against the installed library:

program c
  use heatflow_kinds, only: dp
  implicit none
  print '(a, i0)', 'dp kind = ', dp
end program c
gfortran c.f90 -I/tmp/i3/fortran/modules -L/tmp/i3/lib -lheatflow -o c
./c
dp kind = 8

The module files are compiler-specific: use the same compiler and version for the library and its users, see Modules and Submodules.

Static, shared and monolithic libraries

[library] type selects the output (since 0.12.0), and since 0.13.0 it may be an array:

type Result

"monolithic" (default)

one static archive with the package and all its dependencies, pruned to what the targets use; cannot be combined with other types

"static"

one static archive per package (.a, or .lib on Windows), for integration into other build systems

"shared"

one shared library per package (.so, .dylib, .dll), linked dynamically

["static", "shared"]

both variants at once

With type = ["static", "shared"], a plain fpm build failed in the link of the shared library, because the objects were built without -fPIC (the aarch64 linker named the relocation R_AARCH64_ADR_PREL_PG_HI21 and asked to recompile with -fPIC):

<ERROR> Compilation failed for object " libheatflow.so "
<ERROR> stopping due to failed compilation

With --profile release, which includes -fPIC, the build produced both libheatflow.a and libheatflow.so, and fpm install --profile release --prefix /tmp/i2 installed both to lib/. The installed executable linked the shared library (ldd printed libheatflow.so => not found): fpm sets the library search path itself for fpm run, but an installed executable needs LD_LIBRARY_PATH or an rpath. On Windows fpm also generates an import .lib for every .dll.

Module naming and the registry

Fortran has no namespaces, so two packages that define a module with the same name cannot be linked together. fpm offers a convention that avoids collisions and requires it only for registry packages. The manifest switch is module-naming in [build]: true selects the default prefix, the package name followed by a double underscore (my_pkg, my_pkg__core, never my_pkg__1__2 or a name over 63 characters), and a string selects a custom prefix of letters and digits followed by a single underscore, which the registry reserves per package. With module-naming = true, heatflow, whose modules are called heatflow_kinds and so on, fails:

 ERROR: Module heatflow_io in ././src/heatflow_io.f90 does not match its package name (heatflow).
 ERROR: Module heatflow_kinds in ././src/heatflow_kinds.f90 does not match its package name (heatflow).
 ERROR: Module heatflow_solver in ././src/heatflow_solver.f90 does not match its package name (heatflow).
        Hint: Try disabling module naming in the manifest: [build] module-naming=false .
<ERROR> *cmd_build* Model error: The package contains invalid module names. Naming conventions are being requested.

(Three of the module lines.) With the custom prefix module-naming = "heatflow" the same sources build, since heatflow_kinds is the prefix followed by one underscore. Package names follow the same rules as Fortran names: hyphens read as underscores, a name ending in an underscore or containing __ is invalid.

fpm publish creates a tarball and uploads it, and enforces the convention. It needs a registry account, a namespace, a token (do not share it), a semantic version and an SPDX license in the manifest, and an upload cannot be deleted. --show-package-version, --show-upload-data and --dry-run inspect what would be sent. Two checks were run on heatflow, no upload:

fpm publish --show-package-version
fpm publish --show-upload-data
 0.1.0
<ERROR> The package does not meet the module naming requirements. Please set "module-naming = true" in fpm.toml [build] or specify a custom module prefix.

Publishing itself was not attempted, because the registry did not respond (see Registry dependencies). A top-level module named like the package that re-exports the others is the recommended public API (datetime using datetime__dates and datetime__time), the facade pattern of Modules and Submodules.

Tests, examples and cleaning

fpm test builds and runs the programs of test/; each program is a test, and its exit status is the verdict. The usual structure is the one in the heatflow project: test/check.f90 is the driver, test/test_grid.f90 a module of test cases using test-drive, declared as a dev-dependency (the framework is explained on Testing, Debugging and Profiling). The suites run under any profile or compiler (fpm test --profile ci, fpm test --compiler flang-23 --profile debug), and fpm test --runner "valgrind -q" puts a memory checker around them. A test name selects one program, fpm test check; a name that matches nothing prints <INFO> No tests to run and exits normally.

fpm run --example runs the programs from example/; the do_concurrent_stencil program of heatflow is one.

fpm clean deletes build directories after asking; --skip and --all skip the question, and --all also removes the fetched dependencies. Since 0.13.0 the selective options --test, --apps and --examples delete only the executables of that kind, leaving the objects, so a rebuild only relinks (fpm clean --test printed <INFO> Deleted test target: check). --registry-cache clears the registry cache.

compile_commands.json and editors

Every successful build, or fpm build --list (which downloads dependencies and writes the file without compiling), writes build/compile_commands.json in the Clang compilation database format (since 0.12.0). Each entry has the directory, the argument list and the file, so editors and language servers get the real flags, include paths and module directories without configuration. For heatflow the file had 13 entries (one per source file). It is regenerated at each build, cannot be configured from the manifest, and exists only after a successful build or dry run. fortls and clangd-based editors read it; the setup is on Documentation and Editor Tooling. fpm build --dump model.toml (or a .json+ name) writes fpm’s complete internal model, and +fpm build --show-model prints it, which is the tool for understanding why fpm sees a dependency that you do not.

Plugins

The manifest reserves [extra.fpm.<plugin>] for the configuration of fpm plugins, and the reference calls them a convention for third-party tools. fpm 0.13.0 has no mechanism that dispatches an unknown subcommand to an external program: fpm frobnicate answers <ERROR> unknown subcommand [frobnicate] followed by the usage text, and the subcommand list is fixed. Tools that work with fpm projects, such as formatters, linters, FORD and fortls, therefore run as separate programs and read fpm.toml or compile_commands.json, see Documentation and Editor Tooling.

Continuous integration

The fortran-lang organisation publishes a GitHub Action, setup-fpm, that installs fpm on Ubuntu, macOS and Windows runners. Its release v10 pins fpm 0.13.0, and fpm-version can name another release tag or latest (which needs github-token). A minimal workflow (documented from the action’s README, not run) that installs gfortran, builds, runs the tests, and builds in release mode:

name: ci
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: fortran-lang/setup-fpm@v10
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
      - run: sudo apt-get install -y gfortran
      - run: fpm test --flag "-std=f2023 -Wall -Wextra -pedantic -fcheck=all"
      - run: fpm build --profile release

The fpm documentation

The documentation is a Sphinx site, https://fpm.fortran-lang.org/, whose sources are in the fpm-docs repository.

Page Covers

Installing fpm

every installation channel and the source bootstrap

Tutorials

first steps and adding dependencies (stdlib, test-drive, a command-line parser)

How-to guides

computing pi with OpenMP, selective cleaning

Specifications and reference

the manifest reference, features and profiles, metapackages, compile_commands.json

Registry

settings, package upload, module naming rules

News

release notes for 0.9.0 through 0.13.0

Design documents

design notes for the project

Two small inconsistencies in the pages, found while writing this one: the OpenMP how-to calls the profile option --profile-release, which fpm 0.13.0 rejects (UNKNOWN LONG KEYWORD: --profile-release; the option is --profile release), and the metapackages page describes --runner-args without noting that a value starting with a dash needs the = form shown above.

What each release added

Release Highlights relevant to this page

0.8.2 and 0.8.3

fpm publish to the registry (0.8.2 or later); metapackages (0.8.3 or later)

0.9.1

dependency-specific preprocessor macros

0.11.0

the HDF5 metapackage through pkg-config, installing test programs, parsing of abstract interfaces, a new asset naming convention for setup-fpm

0.12.0

compile_commands.json, static and shared library targets, the BLAS and NetCDF metapackages, external BLAS/LAPACK for stdlib, the --config-file option

0.13.0

features and profiles, CPP conditional compilation during source parsing, multiple simultaneous library targets, Flang as a first-class compiler and amdflang, selective fpm clean, a custom build directory, a custom module install directory, propagation of metapackages through dependencies, macOS ARM64 and Intel release artifacts

fpm, CMake and Meson

The three build systems solve overlapping problems. The choice is mostly about what else the project must build.

Question fpm CMake Meson

Module dependency scan

built in, from the sources

built in for Fortran (Ninja and Makefile generators)

built in

Configuration

short TOML, conventional layout

a script language, full control

a declarative DSL

Fetching dependencies

git, path, registry, metapackages

FetchContent, find_package

subprojects, wraps

Mixed Fortran, C, C++, Python

C and C++ sources are compiled; nothing else

complete

complete

Cross-compilation and complex toolchains

none documented

complete

complete

Best for

Fortran-only libraries and applications, teaching, quick starts

large mixed projects, existing C/C++ ecosystems

mixed projects, f2py and Python extensions

The tools can coexist: the heatflow sources are described by an fpm.toml for everyday development and, in the same tree, by a CMakeLists.txt for integration into larger builds. The comparison, with the same project built by each tool, is on Building with Make, CMake and Meson.

Where the books are outdated

The Fortran books consulted for this section predate fpm or mention only the tool of their time. The current practice:

Area Outdated in the books, current approach

Build tools

the module chapters explain the recompilation cascade under make (Modern Fortran Explained, the chapter on modules) and no build tool beyond it; today fpm, CMake and Meson scan the modules and compute the order, and fpm needs no makefile at all

Package management

no dependency manager is described; fpm fetches git, path and registry dependencies and metapackages, and the community publishes packages on the fortran-lang package index

Testing

no unit-test framework is described; fpm test runs the programs of test/, normally with test-drive

Compiler selection

the books name a compiler per example; fpm selects it with --compiler or FPM_FC and keeps one build directory per compiler

Flags

compiler options are taken from the vendor manual one at a time; fpm profiles and features keep them in the manifest

Pitfalls

  • A plain fpm build passes no warning or checking flags. Name a profile (--profile debug) or pass --flag; check with --verbose.

  • --profile and --features cannot be combined. Put the features into a profile.

  • Profile flags may be duplicated in the compile line when a feature combines base, compiler and metapackage settings. It is harmless; read compile_commands.json.

  • Flang gets no profile flags. In 0.13.0 --profile debug and --profile release left the Flang options empty here; use --flag or a flang section of a feature.

  • A branch or no reference in a git dependency moves. Pin a tag or a full rev; an abbreviated hash fails.

  • A dev-dependency is not exported, so a module used by library code must be a normal dependency.

  • Macro-only profile changes are not tracked reliably. Switching between profiles that differ only in preprocess macros can leave a stale executable; clean or use separate build directories.

  • The mpi metapackage needs implicit-typing = true and implicit-external = true in the main package.

  • external-modules finds nothing. It only silences the dependency scan; add the include paths, the libraries and, for OpenMP, -fopenmp.

  • A shared library needs -fPIC. The plain build lacks it; the debug and release profiles include it.

  • An installed shared library is not on the loader path. Set LD_LIBRARY_PATH or use an rpath.

  • Module names are global. Two packages with a module of the same name collide; use module-naming before publishing.

  • A comment in a module recompiles all its users. Keep implementations in submodules, so that editing them does not cascade.

  • A registry dependency can fail at the network layer. On the day of the check the official registry answered with a server error; use git dependencies.

  • fypp sources are not preprocessed. Generate .f90 files before the fpm build.

  • -C DIR goes after the subcommand, fpm run -C dir; before it, the container’s fpm printed the help text.