CMake .gitignore build directory

CMake can place a .gitignore file in the build directory to allow making build directories with arbitrary names not cause clutter. Many projects have a project-wide .gitignore containing:

build*/

but some users like to use bin/ etc. instead.

Make this more flexible by putting a .gitignore in the build directory, regardless of name by putting in CMakeLists.txt:

# --- auto-ignore build directory
if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore)
  file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*")
endif()

Mac mini M1 value

The Mac Mini M1 is several hundred dollars cheaper than the M1 Macbooks. This and the $599 sale price seem to make the Mac Mini M1 a good value for developers. Homebrew has arm64 binaries. Most programs not specifically for arm64 work through Rosetta with nearly full performance.

Running benchmarks that take about ten minutes, the fan did not come on and the case was still cool to the touch. With 8GB of RAM, some large project require limiting build parallelism to avoid running out of RAM on build.

The M1 Mac mini has a modestly adequate internal speaker suitable for quiet offices. It was disappointing that with MacOS 11.2, the volume was not controllable in MacOS itself with HDMI audio on the same monitor that volume control worked with Raspberry Pi–to be clear, this is an OS setting, not adjusting the monitor itself. It’s also disappointing that a microphone isn’t built in–this would have been useful for Siri at least. I suppose Apple may have felt people would try to use an internal microphone for conferencing, and then get disappointed if they weren’t close enough to it for good sound.

As compared to a $50/month cloud Mac service, the Mac Mini M1 pays for itself in a year, while adding value as a media center and convenience of having a local physical Mac. A a developer, I didn’t feel the M1 laptops were a good value for my use cases, but others will of course enjoy them.

Intel oneMKL LAPACK95 with Gfortran

The Intel oneMKL LAPACK95 library can also be used with Gfortran. Install LAPACK95 to ~/.local/include/ and ~/.local/lib/ by:

cd $MKLROOT/interfaces/lapack95

make libintel64 INSTALL_DIR=~/.local/ interface=lp64 FC=gfortran

An example CMakeLists.txt, using the FindLAPACK.cmake under cmake/Modules of Netlib LAPACK95

list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/")

find_package(LAPACK COMPONENTS LAPACK95 REQUIRED)

add_executable(myexe myprogram.f90)
target_link_libraries(myexe PRIVATE LAPACK::LAPACK95)

Intel compile BLAS95 / LAPACK95 reference

GCC static link missing DLL

Compilers typically have an option to specify static linking of the compiler libraries. This can help mitigate “missing libgfortran” errors, particularly on Windows. This problem arises because the developer may switch compiler environments, or the end user may not have compiler libraries with a compatible ABI on PATH (Windows), LD_LIBRARY_PATH (Linux), LIBRARY_PATH (MacOS).

Build environment switching is especially common on Windows. Relatively few Windows users have compilers installed. Missing DLLs have long been a hallmark of distributing Windows programs in various programming languages. A typical error code is -1073741515 corresponding to hex error code 135.

GCC / Gfortran can statically link the compiler libraries e.g. libgfortran into the final executable. This increases the size of the executable, but the extra space may be negligible compared to the nuisances avoided.

gfortran -static -static-libgfortran myprog.f90 mylib.f90 -o myprog.exe

We often implement GCC static executable linking in CMake like:

Other compilers have similar static compiler library link options:

clang -static

flang -static-flang-libs

ifort -static-intel

We don’t universally apply static linking because some libraries may only be available as dynamic. MacOS static linked executable may only work on your specific computer; MacOS prefers dynamic linking. This technique does not guarantee portable executables, which are a more general problem. Using a slim libc like musl can be part of the solution for portable static executables.

Zlib next generation

The venerable and ubiquitous Zlib compression library fell into non-development several years ago. CMake build deprecation warnings have been emitting that will soon become errors. Not to fear, a new next generation Zlib has been under development, including by original Zlib developers. Keep an eye on zlib-ng as it prepares to take over for the legacy zlib.

CMake generate pkg-config .pc

CMake can generate pkg-config .pc files for packages. The .pc file serves as a Rosetta stone for many build systems. While we normally use the .cmake files for our packages, we also include the .pc files for non-CMake users. Meson has a built-in function for generating pkg-config files, but CMake does not yet.

A good basic reference for pkg-config .pc syntax is helpful. We use a my_package.pc.in template with contents generated by CMake configure_file() and associated variables.

CMake NO_DEFAULT_PATH also disables hints and path suffixes

CMake “find_{file,library,package,path,program}” have a NO_DEFAULT_PATH parameter that disables ALL search paths, even the CMake >= 3.12 <NAME>_ROOT priority. The need for this and the workaround is best shown by example with a non-system compiler such as Intel oneAPI. The reason that we use NO_DEFAULT_PATH for non-system compilers is because CMake will still try to use the system libraries that may not be ABI compatible with the other compiler. NO_DEFAULT_PATH disables the CMake default PATH_SUFFIXES, so those need to be specified as well.

To make the command line and environment variable hints work again, do like:

find_library(ZLIB_LIBRARY
  NAMES z zlib
  NO_DEFAULT_PATH
  HINTS ${ZLIB_ROOT} ENV ZLIB_ROOT
  PATH_SUFFIXES lib)

find_path(ZLIB_INCLUDE_DIR
  NAMES zlib.h
  NO_DEFAULT_PATH
  HINTS ${ZLIB_ROOT} ENV ZLIB_ROOT
  PATH_SUFFIXES include)

Fortran generic interface procedure with optional arguments

Fortran 2018 added syntax useful for polymorphism including select rank, which allows rank polymorphism and select type, which allows class polymorphism. For reference, GCC ≥ 7 has select type and GCC ≥ 10 has select rank. Intel oneAPI supports both.

To be compatible with older compilers as well as handle cases where it’s simply more concise to use Fortran 2003 generic procedure interfaces, each procedure must be unambiguously distinguishable. Several criteria make a procedure distinguishable. Here we focus on having at least one non-optional argument that is TKR-distinct (Type, Kind, Rank).

Notice that this example has the first variable non-optional to make the procedures TKR-distinct.

module mod1

implicit none (type, external)

interface manyranks
  procedure m0,m1
end interface manyranks

private
public :: manyranks

contains

subroutine m0(val1, val2)
real, intent(in) :: val1
real, intent(in), optional :: val2
!! omitted code
end subroutine m0

subroutine m1(val1, val2)
real, intent(in) :: val1(:)
real, intent(in), optional :: val2(:)
!! omitted code
end subroutine m1

end module mod1

The code will compile as above. If you add optional to both “val1”, the compilation will fail like:

  • GCC: Error: Ambiguous interfaces in generic interface 'manyranks' for 'm0' at (1) and 'm1' at (2)
  • Intel: error #5286: Ambiguous generic interface MANYRANKS: previously declared specific procedure M0 is not distinguishable from this declaration. [M1]

Although this example used rank polymorphism, the same issue arises when using any of type, kind or rank (TKR) generic procedures–there must be an unambiguous resolution with at least one non-optional argument. This polymorphism is implemented at runtime, and so there is no guarantee of non-ambiguity when all arguments are optional.

GitHub Actions Windows MSYS2 MS-MPI

Microsoft’s MPI implementation is known as MS-MPI. MSYS2 is a popular choice to use GCC, Clang and many other developer tools from the Windows Command Prompt, PowerShell or the MSYS Terminal itself. Continuous Integration for Windows MPI applications on GitHub Actions is accomplished using a workflow similar to below. This would be in a .github/workflows/ci_cmake.yml file.

NOTE: we set inter-step environment variables using GitHub Actions environment files

Get list of CMake test names

CMake add_test() can be dynamically set by arbitrarily complex foreach(), if(), etc. logic. If it’s known that a subset of test names need parameters such as longer timeouts, these can be set dynamically after the tests are declared. A list of tests enabled is retrieved by the TESTS property. This list is useful to be queried for particular test names as in the example below.

This example supposes most tests are set to a short timeout, but a couple tests “test1” “test2” need a longer timeout. The variable “_test_names” will contain all the test names previously added in the CMakeLists.txt via “add_test()” in a list in the DIRECTORY scope.

get_property(_test_names DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY TESTS)

foreach(l test1 test2)
  if(${l} IN_LIST _test_names)
    set_tests_properties(${l} PROPERTIES TIMEOUT 600)
  endif()
endforeach()

NOTE:

  • get_property(_test_names GLOBAL PROPERTY TESTS) will return an empty list–DIRECTORY scope must be used.
  • The scope is strictly the DIRECTORY specified–tests added earlier in other directories will not be listed. Thus, this method must be applied in each directory where it’s desired to list targets.