CMake find with Homebrew

Anaconda Python puts itself first on PATH when activated. This can become a problem for libraries like HDF5, where “conda install h5py” puts compiler wrapper “h5cc” on PATH first. For systems where Homebrew is used to provide packages to find from CMake, tell CMake to prefer a package location with CMAKE_PREFIX_PATH.

export CMAKE_PREFIX_PATH=$(brew --prefix)

CMake Git inactivity timeout

CMake Git operations such as shallow clone can cause unexpected failures due to too small INACTIVITY_TIMEOUT in ExternalProject or FetchContent. Be sure to set INACTIVITY_TIMEOUT to a large enough value. 15 seconds is too short a timeout for Git shallow clone, for example. Consider 60 seconds or larger INACTIVITY_TIMEOUT.

Also check the user ~/.gitconfig file for timeout:

[http]
	lowSpeedLimit = 1000
	lowSpeedTime = 60

If lowSpeedTime is too short, this download failure can also occur. Set to 60 seconds or more.

CMake generator full path

Normally it is not necessary to specify the path to the CMake generator backend, assuming the generator executable is on the system PATH. For special use cases such as testing CMake with different versions of a generator the generator executable absolute path may be specified via CMAKE_MAKE_PROGRAM. The absolute path to the generator is necessary or CMake will not find it.

Example: GitHub Actions Linux image:

    - name: Install Ninja
      run: |
        curl -O https://github.com/ninja-build/ninja/releases/download/v1.11.0/ninja-linux.zip
        unzip ninja-linux.zip        

    - name: CMake configure
      run: cmake -G Ninja -DCMAKE_MAKE_PROGRAM=$(realpath ./ninja) -Bbuild

Get the full path from a relative path with realpath to Ninja (when necessary) like:

cmake -G Ninja -DCMAKE_MAKE_PROGRAM=$(realpath ./ninja)

On MacOS use Homebrew to install Ninja:

brew install ninja

On Windows use MSYS2 or pip install ninja.

Compiler macro definitions

Compilers define macros that can be used to identify a compiler and platform from compiled code, such as C, C++, Fortran, et al. This can be used for platform-specific or compiler-specific code. If a significant amount of code is needed, it may be better to swap in different code files using the build system instead of lengthly #ifdef logic. There are numerous examples for C and C++ so here we focus on macros of Fortran compilers.

Gfortran compiler macro definitions are obtained in an OS-agnostic way by:

echo "" | gfortran -dM -E - > macros.txt

that creates a file “macros.txt” containing all the compiler macros.

commonly used macros to detect operating system / compiler configuration include:

  • _WIN32 1
  • __linux__ 1
  • __unix__ 1
  • __APPLE__ 1

CAUTION: these macros are actually not available in the Gfortran compiled programs as they are in GCC. A workaround is to have the build system define these for the particular compiler, OS, etc.


Intel oneAPI classic compiler macros include the Gfortran macros noted above and additionally:

  • __INTEL_COMPILER 1

Intel oneAPI Next Gen LLVM compiler macros set:

  • __INTEL_LLVM_COMPILER 1

to distinguish from oneAPI Classic compilers.


Cray ftn Fortran compiler macros include:

  • _CRAYFTN

Nvidia HPC Fortran compiler macros are printed by:

nvfortran -dM

Nvidia HPC macros include:

  • __NVCOMPILER
  • __NVCOMPILER_LLVM__

Flang macros include

  • __FLANG 1

Other Fortran compiler macros that identify the compiler and platform can be found in CMake source code.

CMake FindPython hints

CMake Find modules are by their nature a little aggressive about finding libraries and executables. This becomes a factor on Windows in particular when Anaconda Python is not active in the current Terminal. CMake find_package(Python) will by default still prefer Anaconda Python, but it won’t operate correctly without conda activate, which presumably the user has either forgotten to do or doesn’t desire at the moment. To decrease the aggressiveness and find Windows Store Python etc. when conda isn’t activated on Windows, add to the project CMakeLists.txt before find_package(Python):

set(Python_FIND_REGISTRY LAST)
# this avoids non-active conda from getting picked anyway on Windows

find_package(Python)

Disable conda auto activate base

Anaconda Python by default auto-activates the “base” environment each time a new Terminal is opened. This slows opening new Terminals, particularly on systems with slow or virtual disks. Particularly if the user isn’t constantly using Python, it can be beneficial to make conda only active when specified. To disable conda auto-activation on new Terminals, type:

 conda config --set auto_activate_base false

Fortran compiler standard enforce

Fortran compilers typically have options for enforcing Fortran standards. The compilers raise additional warnings or errors for code that is not deemed compliant. Fortran standard options can make false warnings, so we generally do not enable standards checking for user defaults. However, we do enforce implicit none as a quality measure.

It’s also important to use implicit none so that each variable must be assigned beforehand. We recommend the Fortran 2018 statement:

implicit none (type, external)

which requires explicitly defined procedure interfaces as well.

type
the traditional implicit none default
external
new for Fortran 2018, requires explicit interface for external procedures.

GCC Gfortran -std=f2018 enforces Fortran 2018 standard. Consider these Gfortran options:

gfortran -Wall -fimplicit-none

Intel oneAPI -stand f18 enforces Fortran 2018 standard. Consider these options that also enforce implicit none:

ifx -warn

Cray Fortran compiler enforces implicit none via option:

ftn -eI

note that’s a capital “I” not a lowercase “ell”.

Nvidia HPC Fortran compiler enforces implicit none via:

nvfortran -Mdclchk

NAG Fortran has -f2018 Fortran 2018 flag. Enforce implicit none by:

nagfor -u

CMake logic to enforce these standards:

if(CMAKE_Fortran_COMPILER_ID STREQUAL "Cray")
  add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-eI>")
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
  add_compile_options(-Wall "$<$<COMPILE_LANGUAGE:Fortran>:-fimplicit-none>")
elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel")
  add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-warn>")
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "NVHPC")
  add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-Mdclchk>")
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "NAG")
  add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-u>")
endif()

Major changes in GCC Gfortran by version

GCC Gfortran and Intel oneAPI are the most advanced, widely available modern Fortran compilers. Useful Fortran 2018 enhancements include: select rank assumed array rank, error stop within pure procedures, random_init to initialize random number seed, and implicit none (type, external) to require external procedures to be explicitly declared. GCC 10 is the oldest version currently maintained. Intel oneAPI has full Fortran 2018 support.

To get recent GCC is usually straightforward. Red Hat should use GCC Toolset. MacOS Homebrew quickly adds the latest GCC version. If Ubuntu gfortran repo defaults aren’t adequate, get recent Gfortran via PPA.

Here are some of the major changes in Gfortran by version:

  • Gfortran 12 enhances OpenMP 5 and OpenACC 2.6 support. Numerous bugfixes. bind(C) with character length greater than one.
  • Gfortran 11 completed OpenMP 4.5 support
  • Gfortran 10 added select rank
  • Gfortran 9 added random_init() to initialize the random generator seed.
  • Gfortran 8 added automatic nested loop exchange with do concurrent, actual argument array with too few elements for dummy argument now errors, initial support for parameterized derived types (simply define kind at initialization) and coarray support for teams. Standard flag -std=f2018 added and deprecated -std=f2008ts.
  • Gfortran 7 added derived type IO select type. Complete Fortran 2003 support, Fortran 2018 non-constant stop and error stop codes, and -fdec- options to help compile very old non-standard code.

Gfortran 6 added Fortran 2008 submodule support, useful for large projects to save compilation time and allow powerful use scenarios. Fortran 2003 deferred-length character are useful for avoiding bothersome trim() everywhere.

GCC 5 added full support for OpenMP 4.0, Fortran 2003 ieee_ intrinsics, Fortran 2008 error stop in pure procedures with constant error code. GCC 4.9 added Fortran 2003 deferred-length character variables in derived types. GCC 4.8 supported Fortran 2008 polymorphism, including select type, class(*), type(*), and assumed rank dimension(..). GCC 4.6 was the first version of Gfortran reaching beyond Fortran 95, with Fortran 2003 deferred-length character variable and Fortran 2008 impure elemental support. GCC 4.5 added Fortran 2008 iso_fortran_env. GCC 4.4 added initial support for polymorphism and OpenMP 3.

CMake allows switching parameters based on compiler version. This is very useful for modern Fortran programs.

Example CMakeLists.txt for Fortran compiler version dependent options.

if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
  add_compile_options(
  -Wall
  $<$<COMPILE_LANGUAGE:Fortran>:-fimplicit-none>
  )
endif()

add_executable(myprog main.f90)

Reference: Gfortran changelog