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.
+ 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 |
MSYS2 (Windows) |
|
WinGet (Windows) |
|
Homebrew (macOS) |
|
MacPorts |
|
PyPI |
|
Conda |
|
Spack |
|
Arch Linux AUR |
|
OpenBSD |
the |
From source |
|
Source: fpm: Installing fpm
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
Source: Fortran Package Manager
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 |
|---|---|
|
the manifest; the only required file is this one, containing at least |
|
the library: every module and procedure file; compiled into |
|
executables: every file containing a |
|
tests: programs run by |
|
examples: programs run by |
|
files for the Fortran |
|
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!
Source: fpm: First steps with fpm
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 |
|---|---|
|
only |
|
only |
|
only |
|
only |
|
an extensively annotated |
|
only |
|
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 |
|---|---|
|
create a package directory with sample files (above) |
|
fetch dependencies, scan the sources, compile and link everything the targets need |
|
build and run the executables in |
|
build and run the programs in |
|
install executables, and with |
|
delete the build directories, with a confirmation prompt unless |
|
re-resolve and re-fetch dependencies |
|
upload the package to the registry |
|
a one-line description of every subcommand |
|
the manual page of a subcommand, for example |
The options shared by build, run, test and install, from fpm build --help and the sibling help pages, are:
| Option | Meaning |
|---|---|
|
a built-in compilation profile ( |
|
comma-separated features from the manifest; cannot be combined with |
|
extra Fortran, C, C++ and linker flags |
|
choose the tools |
|
use another build directory (default |
|
do not drop unused module dependencies (see below) |
|
list targets without building or running them; for |
|
show the commands fpm runs |
|
another global configuration file |
|
on |
|
prefix the program with a command such as a debugger, |
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"
Source: fpm: Manifest reference
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
Source: fpm: First steps with fpm
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) |
|---|---|
|
|
|
|
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 |
|---|---|---|
|
|
the Fortran compiler (default |
|
|
Fortran compiler flags |
|
|
the C compiler and its flags |
|
|
the C++ compiler and its flags |
|
|
the archiver |
|
|
extra linker arguments |
|
|
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.
Source: fpm: Features and Profiles
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 |
|---|---|
|
required; identifies the package, and is the default name of the library and of the executable built from |
|
a string, preferably a Semantic Versioning number; it may instead name a file relative to the project root that contains the version |
|
an SPDX identifier such as |
|
free text; |
|
plain-text summary (no markup), a list of categories (one is preferred), a list of keywords and a URL |
Source: fpm: Manifest reference
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: fpm: Manifest reference
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"
Source: fpm: Manifest reference
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
Source: fpm: Manifest reference
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.
Source: fpm: Manifest reference
The build table
| Key | Meaning |
|---|---|
|
|
|
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) |
|
names of modules that come from outside the package (for example |
|
|
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"
Source: fpm: Manifest reference
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"
Source: fpm: Manifest reference
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"
Source: fpm: Manifest reference
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 |
|---|---|
|
|
|
|
|
|
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"
Source: fpm: Adding dependencies
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 |
|---|---|---|
|
adds the fortran-lang standard library as a git dependency; with |
yes, below |
|
adds the modernised fortran-lang MINPACK (release |
no |
|
adds the compiler’s OpenMP flags to compiling and linking |
yes, with gfortran |
|
finds MPI compiler wrappers ( |
yes, with MPICH |
|
finds HDF5 through |
no |
|
finds NetCDF through |
no |
|
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
Source: fpm: Features and Profiles
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.
Source: fpm: Features and Profiles
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
Source: fpm: Manifest reference
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 |
|---|---|
|
one static archive with the package and all its dependencies, pruned to what the targets use; cannot be combined with other types |
|
one static archive per package ( |
|
one shared library per package ( |
|
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.
Source: fpm: Module name requirements and fpm: Package upload
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
Source: fortran-lang/setup-fpm
The fpm documentation
The documentation is a Sphinx site, https://fpm.fortran-lang.org/, whose sources are in the fpm-docs repository.
| Page | Covers |
|---|---|
every installation channel and the source bootstrap |
|
first steps and adding dependencies (stdlib, test-drive, a command-line parser) |
|
computing pi with OpenMP, selective cleaning |
|
the manifest reference, features and profiles, metapackages, |
|
settings, package upload, module naming rules |
|
release notes for 0.9.0 through 0.13.0 |
|
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 |
|
0.9.1 |
dependency-specific preprocessor macros |
0.11.0 |
the HDF5 metapackage through |
0.12.0 |
|
0.13.0 |
features and profiles, CPP conditional compilation during source parsing, multiple simultaneous library targets, Flang as a first-class compiler and |
Source: fpm: Fpm version 0.13.0 released, 0.12.0 and 0.11.0
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 |
|
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 |
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; |
Compiler selection |
the books name a compiler per example; fpm selects it with |
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 buildpasses no warning or checking flags. Name a profile (--profile debug) or pass--flag; check with--verbose. -
--profileand--featurescannot 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 debugand--profile releaseleft the Flang options empty here; use--flagor aflangsection 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
preprocessmacros can leave a stale executable; clean or use separate build directories. -
The
mpimetapackage needsimplicit-typing = trueandimplicit-external = truein the main package. -
external-modulesfinds 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_PATHor use anrpath. -
Module names are global. Two packages with a module of the same name collide; use
module-namingbefore 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
.f90files before the fpm build. -
-C DIRgoes after the subcommand,fpm run -C dir; before it, the container’s fpm printed the help text.
See also Best Practices and Style and Common Gotchas and Pitfalls.
References
-
Fortran Package Manager: home and documentation and fortran-lang/fpm on GitHub
-
fpm: Manifest reference and manifest-reference.md at v0.13.0
-
fpm: Features and Profiles and Built-in dependencies (metapackages)
-
fpm: Registry, Settings, Package upload and Module name requirements
-
fpm: Installing fpm, First steps with fpm and Adding dependencies
-
fpm: Cleaning build artifacts with fpm and Computing PI with OpenMP
-
Fortran Programming Language: Packages and Building programs
-
GNU Fortran manual: Invoking GNU Fortran and Flang documentation: Flang driver