Scientific Computing

CMake Ninja generator preprocesses Fortran

CMake with the Ninja and Ninja Multi-Config generators virtually always work well together, including with Fortran projects. To generate the dependency graph, CMake directs the Fortran compiler to preprocess each Fortran code file when using the Ninja generator. In rare cases, some legacy Fortran large code files can generate unexpected and spurious syntax errors when using CMake + Ninja.

The workaround seems to be to use the GNU Make backend, we have found that works for the rare case this became an issue.

Android switch to 3-button navigation

Android task switching swipe navigation can be switched to 3-button navigation. Some workflows are much easier via the traditional 3-button navigation used by Android since Android 3.x / 4.x. Also when wearing gloves, using 3-button navigation can work better.

Switch an Android device to traditional 3-button navigation (back, home, switch or triangle, circle square) by going to Settings → System → Gestures → System navigation and select “3-button navigation”.

CMake Fortran design patterns

CMake is excellent for building very complex Fortran projects across operating systems and computing platforms from embedded systems to HPC. Here are a few common Fortran CMake patterns.

specify linker options for all targets using add_link_options()

add_link_options(-myflag)

or for a particular target with target_link_options

For hand-written Makefile the order of libraries matters completely. CMake attempts to determine the graph for library files, starting from the user specified order. CMake will try a finite multiplicity to resolve the graph, but in tough cases of library interdependency library ordering may need to be influenced by add_dependencies() or even object libraries.

“Undefined reference to” occurs at linking of main executable: be sure the library is actually linked to the user library with

cmake --build build -v

Fortran module include: be sure the *.mod file directory is included, particularly to the main executable by setting the target properties like:

include(GNUInstallDirs)

target_include_directories(mymod INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR})

set_property(TARGET mymod PROPERTY Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR})

this can be done for numerous targets in a loop instead of repeatedly specifying by a foreach() loop:

foreach(t foo bar biz baz)
  target_include_directories(${t} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR})

  set_property(TARGET ${t} PROPERTY Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR})
endforeach()

To ensure the build system is completely updated, do

cmake --build build --clean-first

Specify C and CXX for CMake projects with MSVC

A quirk with Visual Studio and CMake for C++ projects is that the C language must also be specified in CMakeLists.txt like:

project(foo LANGUAGES C CXX)

Otherwise, weird linker errors can occur, even on a pure C++ project with no C files.

CMake FindHDF5 improvements

An improved FindHDF5.cmake in h5fortran handles modern HDF5 versions including on Windows.

The important improvements are:

  1. check that HDF5 compiler wrapper is able to compile and link a simple HDF5 program
  2. have more default directories to search for the HDF5 libraries
  3. work with HDF5 across versions
  4. work with HDF5-MPI

Before this, CMake would get confused by the Anaconda Python HDF5 compiler wrapper. This also makes Intel compiler on Windows work with HDF5.

Benefits of CMake Object Libraries

CMake Object Libraries can be used to keep build directories less cluttered and speed up the build process. The traditional workflow for Makefile was to create lots of object files (targets) with the relevant compile options, definitions and link flags. Then the final executable or library targets would be linked from all the object files.

CMake add_library() defaults to creating static libraries. The following stanza creates “libfoo.a” or similar:

add_library(foo src.f90)

With numerous targets, there are a lot of “lib*.a” that may be cluttering the build directory. Also, there is a finite time consumed in creating all those archive .a files from the object files. For projects that use Visual Studio, using Object Libraries can help avoid complications with exporting symbols for MSVC.

CMake object libraries: a cleaner option for most cases where the “lib*.a” artifact is not needed is to create CMake Object Libraries instead:

add_library(foo OBJECT src1.f90 src2.f90)
add_library(bar OBJECT src3.c src4.c)

add_executable(baz main.cpp $<TARGET_OBJECTS:foo> $<TARGET_OBJECTS:bar>)

instead of having files under build/ like “libfoo.a, libbar.a” there will be only “baz.exe”. The object files will be tucked away under build/CMakeFiles/foo/*.o and build/CMakeFiles/bar/*.o. The generator expressions expand to all the object files of “foo” and “bar” like a legacy Makefile.

The INTERFACE target properties of object libraries propagate up to targets linking them as usual. You may need to modify the PUBLIC / PRIVATE / INTERFACE of object libraries or add them to the “target_link_libraries()” of the top-level targets when going from static or shared libraries to object libraries.

Caveats: object libraries with duplicate symbols can cause conflicts that need to be manually resolved. When linking non-object libraries, the linker typically just uses the first instance seen of a symbol.

Update Raspberry Pi to latest OS

It’s worthwhile to upgrade Raspberry Pi OS to have recent Python with system / GPIO libraries. These Python-tied libraries are not always easy to install otherwise. It is generally not worthwhile to upgrade the OS in place. Instead, copy off your files, do a fresh Debian install, and copy your files back. Keep the original SD card in case some setup is hard to figure out.

The best way to upgrade the Raspberry Pi operating system is get a new SD card and load the OS from scratch as described below.

Copy any wanted files from Raspberry Pi to the PC with sftp or rsync. This is in case the original SD card fails.

Setup SD card: the contents of the SD card are erased in the following procedure. Buy a new SD card so that the original SD card remains with a working configuration in case there’s an issue.

Use the Raspberry Pi Imager to select Raspberry Pi OS Lite. BEFORE writing the OS image to SD card, enable SSH server and other desired options.

Headless Raspberry Pi first boot: without a monitor and keyboard on the Pi, the Avahi dæmon on the Raspberry Pi will make it easy to find your Raspberry Pi when you plug into LAN or connect WiFi.

Put the SD card into the Pi, plug an Ethernet cord into the LAN router/switch and from the PC (username, hostname are those picked for the Pi by the Raspberry Pi Imager program):

ssh username@hostname.local

Update Pi software

apt install rpi-update
rpi-update
apt update
apt upgrade

Raspberry Pi Configuration

raspi-config

Raspberry Pi reliable long-term install tips: avoid overclocking the Raspberry Pi for best long-term reliability. Use a heatsink kit for high-stakes remote operations.


Related: completely headless Raspberry Pi setup

Git reword prior commit messages

Git history can be rewritten with certain caveats. One reason to rewrite history for feature requests is to have project-specified message formatting. Another is to add long comments, denoted by adding at least one blank line beneath the minimal summary message.

To edit messages of prior commits, use git rebase -i and then select “r” or “reword” for the desired commits. This is quicker and easier than “e” or “edit” that is used for editing code or breaking up prior commits.

Fix MPI_Z_LIBRARY CMake error

OpenMPI uses “libz” internally. For the past few years, using OpenMPI from Cygwin also requires:

setup-x86_64.exe -P libopenmpi-devel zlib-devel

Otherwise, when building with OpenMPI with CMake OpenMPI might not be found due to:

Missing MPI_Z_LIBRARY

This might occur on other platforms where libz development libraries are not available when building with libopenmpi.

GNU Make parallel build

GNU Make is an important legacy build system. Ninja build system is recommended in general since it’s faster, has more features and generally operates more correctly. Ninja works with CMake and Meson among others.

In general, build systems are useful to:

  • build programs much faster (10’s or 100’s of times faster) by:
    • only recompile parts of the code that changed
    • compile / link in parallel
  • avoid copy-paste mistakes with excessively long compile commands or piles of Bash scripts

GNU Make parallel builds: by default, GNU Make only uses one thread. For most programs, speeding up compilation is desirable and accomplished via the -j or --jobs option:

make -j

The -j option alone tries to compile all targets in parallel at once. This is typically not desirable or efficient due to excessive context switching. On some systems such as Windows Subsystem for Linux, the bare make -j -l2 may overwhelm the computer because WSL1 doesn’t seem to correctly report the load factor. Thus to do parallel builds with GNU Make on WSL1, manually set the number of parallel build threads perhaps equal to or less than the number of physical cores in your PC. For example an 8 physical core laptop would compile with make -j8.

Caveats: in certain systems like older Raspberry Pi computers, the CPU may go into thermal cutback or suffer undervoltage spikes if using an inadequate power adapter. While the better solution is to provide adequate DC power and CPU cooling, one may choose to compromise by manually specifying the number of threads. For example, with the Raspberry Pi we often use 1 or 2 threads.

make -j1

# or

make -j2

RAM limitation: make -j does not consider system RAM, so your computer may become overwhelmed. Try make -j2 or so until the system memory isn’t over-consumed.

Keeping system responsive: a possible downside of building large programs with make -j is that the computer can become non-responsive to input including mouse and keyboard. A symptom of this is that the Caps Lock keyboard light is very slow to respond. To mitigate the problem automatically, disallow a new thread to start if the system becomes too heavily loaded. This is accomplished with the -l or --max-load option.

Linux system load of 0 to 1 means approximately that no tasks are waiting. So if you want to keep your computer responsible to user input (you can continue using your laptop while big program builds in the background), consider something like -l 1. You can set the -l factor higher to allow make to have increasingly higher priority over other tasks, including user input.


Instead of writing Makefiles, we use CMake or Meson on projects involving compiled code. CMake and Meson each have an integrated parallel-executing test suite, which works very well with continuous integration systems for automated self test. Continuous integration should be a part of all software projects of all sizes, from 50 line programs to operating systems. We recommend Azure Pipelines or GitHub Actions for CI in general.

GNU Make parallel docs

Related: CMake fundamentals