CMake Ninja generator always 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.

Windows set WiFi preference order

Windows can control WiFi from the Command Prompt, including WiFi AP preference order. Via the Windows WiFi settings GUI page, the latest added WiFi pushes to the top of the preference stack. This is typically not desired, as when setting up a device in general, one usually connects to the most needed WiFi AP first, and over time adds temporary or less desired WiFi, which Windows keeps at the top of the WiFi preference list. This netsh command prompt procedure allows correcting the Windows WiFi preference order.

Show known WiFi AP

This command lists the known (previously connected) WiFi AP, just like shown in the Windows WiFi settings GUI:

netsh wlan show profiles

The WiFi AP are listed in preference order, from most preferred at the top to least preferred at the bottom. A key piece of information needed from this command is:

Profiles on interface “Wi-Fi”

The name might be “Wi-Fi 2” or other if multiple WiFi adapters are in the PC.

Set WiFi preference order

Using the information from:

netsh wlan show profiles

Put the WiFi APs in the desired priority order like:

netsh wlan set profileorder my_work_wifi Wi-Fi 1

netsh wlan set profileorder my_home_wifi Wi-Fi 2

where the trailing integer is the position in the Windows WiFi preference.

Notes

Android 10 restore 3-button navigation

Android 10 defaults to task switching swipe 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 10 device back 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 per-language option flags

CMake can set compiler flags with three broad scope categories. We set global and per-language options near the beginning of the top-level CMakeLists.txt, before any targets are declared, to avoid confusion about scope. COMPILE_DEFINITIONS works in a similar fashion.

Note: where using FetchContent, add_compile_options() can only be overridden for the fetched project by using the “negative” option in the fetched project. While the per-language method can be overridden just by setting it again in the fetched project. For example, the main project may desire -Wall but this may cause megabytes of warnings from a legacy Fetched project. In this case, we suggest using the per-language rather than global method.

Example

This example is for a C and Fortran project, where some flags apply to C and Fortran, and other flags are Fortran-specific.

project(Foo
LANGUAGES C Fortran)

if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU)

  # options applying to any project language for this compiler
  add_compile_options(-march=native)

  # Fortran-specific, note LEADING space
  string(APPEND CMAKE_Fortran_FLAGS " -fimplicit-none")

  # options for Debug build type
  string(APPEND CMAKE_Fortran_FLAGS_DEBUG " -Werror=array-bounds")

  if(CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL 8)
    string(APPEND CMAKE_Fortran_FLAGS " -std=f2018")
  endif()

endif()


add_library(old OBJECT legacy.f old.f)
# these options apply only to target "old"
target_compile_options(old PRIVATE -w -fno-implicit-none)

add_executable(main main.f90)
target_link_libraries(main old)

Writing image stack with HDF5

The HDF5 format can be used for almost anything, including image stacks. To signal graphical interactive HDF5 viewers that a dataset is an image stack, add HDF5 image metadata, as in this Python h5py example:

import numpy as np
import h5py

# images is a numpy ndarray N x X x Y images

with h5py.File("image.h5", "w") as f:
    h = f.create_dataset("/images", data=images)
    h.attrs["CLASS"] = np.string_("IMAGE")
    h.attrs["IMAGE_VERSION"] = np.string_("1.2")
    h.attrs["IMAGE_SUBCLASS"] = np.string_("IMAGE_GRAYSCALE")
    h.attrs["DISPLAY_ORIGIN"] = np.string_("LL")
    h.attrs["IMAGE_WHITE_IS_ZERO"] = np.uint8(0)

HDFview will show an optional video player for this dataset due to the image metadata added.

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.

Linker options

specify linker options for all targets using add_link_options()

add_link_options(-myflag)

or for a particular target (or static linkers) with

set_target_properties(myexe PROPERTIES STATIC_LIBRARY_OPTIONS -myflag)

Order of libraries

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:

target_include_directories(mymod INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)
set_target_properties(mymod PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include)

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}/include)
  set_target_properties(${t} PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include)
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 2019 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

Our CMake FindHDF5 MR is included in CMake ≥ 3.18. We have made an even further improved FindHDF5.cmake in h5fortran and see how it’s used in hdf5.cmake.

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 1.10.6

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

Using Python in CMake script

Finding Python is made more robust in CMake >= 3.15, which prioritizes location over version number. Prior to CMake 3.15, even specifying Python3_ROOT could be overriden if the other Python was a higher vrsion.

Using the Python interpreter in CMake should generally be via PYTHON3_EXECUTABLE and not Python3::Interpreter. CMake provides the imported target Python3::Interpreter only when the CMAKE_ROLE is PROJECT. This means that Python3::Interpreter is not available when using CTest, which is often when using the Python3 interpreter is desired. Normally, to use Python interpreter from a CMake script, including in execute_process or add_test, use PYTHON3_EXECUTABLE.

Example

find_package(Python3 COMPONENTS Interpreter)

add_test(NAME Foo COMMAND ${PYTHON3_EXECUTABLE} myscript.py -arg1 bar)

CMake policies vs. minimum CMake version

CMake policies typically default to OLD behavior compatible with prior versions of CMake. However, cmake_minimum_required() automatically sets all the CMake policies to “new” through the minimum CMake version. For example, a project with cmake_minimum_required(VERSION 3.14) implicitly sets to NEW all CMake policies CMP0088 and older.

Tradespace

New versions of CMake don’t get the benefits of new behaviors until specifying the new behavior is OK, either implicitly via cmake_minimum_required() or explicitly via cmake_policy(). The downsides of a too-old cmake_minimum_required() include that you have to set if(CMAKE_VERSION VERSION_GREATER_EQUAL ...) statements around extra cmake_policy() statements.

Important policies

Here are a few CMake policies we generally use, typically implicitly by choosing at least CMake 3.13:

cmake_minimum_required(VERSION 3.13)
  • CMP0074 find_package uses PackageName_ROOT variables
  • CMP0076 target_sources() command converts relative paths to absolute. This allows specifying targets in the top-level CMakeLists.txt while having add_subdirectory() containing their sources.
  • CMP0077 option() honors normal variables. Does not manipulate variables when user has defined their value.

Deprecation

CMake gradually deprecates old policies, meaning the deprecated policies default to NEW behavior. For example, CMake 3.18 deprecates CMP0071 and older.