Stop shell script on exception

Shell scripts can stop upon exception rather than handling each one manually. This can significanlty reduce logical clutter in a script. Stop a shell script on error for Unix / Linux / MacOS shell by setting near the top of the .sh script file:

set -o errexit

This is a human-readable equivalent to set -e This works for commonly used Unix shells including Bash and Zsh.

Another useful option is to stop the script if any script variables are defined:

set -o nounset

Stop executing a Powershell script upon exception by adding near the top of the .ps1 Powershell script:

$ErrorActionPreference = 'Stop'

Install Windows Subsystem for Linux

Ubuntu LTS releases among other Linux distros are available on the Microsoft Windows Store or command prompt. Install WSL2 by:

wsl --install Ubuntu

WSL images can be switched between WSL1 and WSL2, but for most purposes WSL2 is generally preferred. WSL can use X11 GUI with programs like Spyder.

Verify if on WSL1 or WSL2 by from PowerShell / Command Prompt:

wsl --list --verbose

The result will be like:

  NAME      STATE           VERSION
* Ubuntu    Running         2

Convert an existing WSL1 distro to WSL2 from PowerShell:

wsl --set-version Ubuntu 2

Install, list, and switch between Linux distros on Windows default for bash by from Command Prompt:

wslconfig

configure

Limit the amount of RAM WSL2 can use by editing Windows file ~/.wslconfig to include:

[wsl2]
swap=0GB
memory=4GB  # arbitrary, set to less than your total computer physical RAM to help avoid using Windows swap

A WSL default that is confusing and slows down WSL program-finding is stuffing Windows PATH into WSL PATH. We normally disable Windows PATH injection into WSL, because it also breaks library finding in build systems like CMake. Additionally, we enable filesystem metadata, as weird permission errors can occur, even causing CMake to fail to configure simple projects.

Each Linux distro has its own /etc/wsl.conf We typically include in our /etc/wsl.conf:

[automount]
enabled = true
options = "metadata"

[interop]
enabled=false
appendWindowsPath=false

The Windows file ~/.wslconfig file sets parameters for all Linux distros, versus the per distro /etc/wsl.conf discussed above. To avoid the use of Linux swap and excessive memory thrashing we include in Windows ~/.wslconfig:

[wsl2]
swap=0GB

Run Ubuntu apps from Windows Command Prompt or PowerShell:

wsl ls -l

Run Windows program from Ubuntu terminal:

/mnt/c/Windows/System32/notepad.exe

Note that capitalization matters and .exe must be at the end.


If necessary to reinstall Ubuntu, copy off your Linux user files as the next step deletes them. From Command Prompt:

Ubuntu clean

Ubuntu

Notes

Homebrew binary bottle download

Downloading binary Homebrew bottles without installing Homebrew can be useful to check the location of the bottle contents. This is useful when developing Meson native-files or CMake Find*.cmake modules.

Homebrew distributes bottles from GitHub Packages. For example, HDF5 binary “bottle” may be inspected by:

tar --list -f <filename>

No Homebrew install is necessary for inspection. Using the libraries and binaries is best done by installing Homebrew.

Keep program running after disconnect

Screen is a terminal multiplexer program available for Linux, MacOS, BSD and similar. Screen allows programs to continue running after a remote user disconnects. If a remote connection is lost unintentionally, screen may not allow reconnection by default by the usual

screen -list

screen -r <id>

normally allows reconnecting to a remote session after logging off. When a connection is lost before disconnecting from screen, you may need the “-x” option:

screen -x <id>

A downside of screen is the difficulty scrolling back in history. Although screen is a mature project, development is still ongoing.

Screen is a terminal multiplexer, and some prefer tmux over screen. Another option is using nohup.

Avoid overriding CMake default install prefix

CMake FetchContent is useful to incorporate subprojects at configure time. FetchContent subproject cache variables can override the top-level project cache, which can be confusing. A particular instance we’ve found problematic is overriding the CMake default install prefix. A top level project desiring to install to the CMake default location will get a surprising result if the child project overrides this.

# DON'T DO THIS

if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  # will not take effect without FORCE
  set(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR} CACHE PATH "Install top-level directory" FORCE)
endif()

Instead, we recommend projects, including projects intended to be consumed via FetchContent use CMakePresets.json to set the default install directory:

{
  "version": 3,

"configurePresets": [
{
  "name": "default",
  "binaryDir": "${sourceDir}/build",
  "installDir": "${sourceDir}/build"
}
]
}

CMake FetchContent vs. ExternalProject

Making multiple software projects work together is readily done by the build system:

instead of Git submodule or monorepo.

Meson subproject and CMake ExternalProject keep project namespaces separate. Meson subproject and CMake FetchContent download and configure all projects at configure time. CMake FetchContent comingles the CMake project namespaces. FetchContent can be easier to use than ExternalProject if you control both software projects' CMake scripts. If you don’t control the “child” project, it may be better to use ExternalProject instead of FetchContent.

For these examples, suppose we have a top-level project “parent” and a “child” project containing a library that is desired in parent. Suppose the child project can be built standalone (by itself) but also may be used directly from other CMake projects.

project CMAKE_SOURCE_DIR CMAKE_BINARY_DIR PROJECT_SOURCE_DIR
parent ~/foo ~/foo/build ~/foo
child: standalone ~/bar ~/bar/build ~/bar
child: CMake ExternalProject ~/foo/build/child-prefix/src/child ~/foo/build/child-prefix/src/child-build ~/foo/build/child-prefix/src/child
child: CMake FetchContent ~/foo ~/foo/build ~/foo/build/_deps/child-src

FetchContent

FetchContent populates content from the other project at configure time. FetchContent populates the “child” project with default values from the “parent” project. Varibles set in the “child” project generally do not affect the “parent” project unless specifically used from the “parent” project.

From “parent” project CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)
project(parent Fortran)

include(FetchContent)
FetchContent_Declare(child
  GIT_REPOSITORY https://github.invalid/username/child.git
  GIT_TAG develop   # it's much better to use a specific Git revision or Git tag for reproducibility
)

FetchContent_MakeAvailable(child)

# your program
add_executable(myprog main.f90)
target_link_libraries(myprog mylib)  # mylib is from "child"
FetchContent_MakeAvailable
make “child” code configure, populating variables and targets as if it were part of “parent” CMake project.

suppose “child” project CMakeLists.txt contains:

project(child LANGUAGES Fortran)

add_library(mylib mylib.f90)
target_include_libraries(mylib INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)
set_target_properties(mylib PROPERTIES
  Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include)

The child project CMAKE_BINARY_DIR and CMAKE_SOURCE_DIR will be those of parent project. That is, if the parent project is in ~/foo and the build directory is ~/foo/build, then the child project in ~/childcode called by FetchContent will also have CMAKE_SOURCE_DIR of ~/foo and CMAKE_BINARY_DIR of ~/foo/build. So be careful in the child project when using such variables that may be defined by parent projects. This is why projects that aren’t specifically designed to work together may be better joined by ExternalProject. A typical technique within the child project that can operate standalone is to refer to CMAKE_CURRENT_SOURCE_DIR instead of CMAKE_SOURCE_DIR as the latter will break when used from FetchContent.

IMPORTANT: When using if() clauses to determine execution of FetchContent, ensure that the FetchContent stanzas are executed each time CMake is run. Otherwise, the FetchContent targets may fail to be available or may have missing target properties on CMake rebuild.

ExternalProject

ExternalProject populates content from the other project at build time. This means the other project’s libraries are not visible until the parent project is built. Since ExternalProject does not combine the project namespaces, ExternalProject may be necessary if you don’t control the other projects.

ExternalProject will not activate without the add_dependencies() statement. Upon cmake --build of the parent project, ExternalProject downloads, configures and builds.

From “parent” project CMakeLists.txt:

project(parent LANGUAGES Fortran)

include(ExternalProject)

set(mylist "a;b;c")
# passing a list to external project is best done via CMAKE_CACHE_ARGS
# CMAKE_ARGS doesn't work correctly for lists

set_directory_properties(PROPERTIES EP_UPDATE_DISCONNECTED true)
# don't repeatedly build ExternalProjects.
# dir prop scope: CMake_current_source_dir and subdirectories

set(child_ROOT ${PROJECT_BINARY_DIR}/child)

ExternalProject_Add(CHILD
  GIT_REPOSITORY https://github.com/scivision/cmake-externalproject
  GIT_TAG develop  # it's much better to use a specific Git revision or Git tag for reproducability
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${child_ROOT}
  CMAKE_CACHE_ARGS -Dmyvar:STRING=${mylist}   # need variable type e.g. STRING for this
  CONFIGURE_HANDLED_BY_BUILD ON
  BUILD_BYPRODUCTS ${child_ROOT}/${CMAKE_STATIC_LIBRARY_PREFIX}timestwo${CMAKE_STATIC_LIBRARY_SUFFIX}
)

file(MAKE_DIRECTORY ${child_ROOT}/include)  # avoid race condition

add_library(timestwo STATIC IMPORTED GLOBAL)
set_target_properties(timestwo PROPERTIES
  IMPORTED_LOCATION ${child_ROOT}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}timestwo${CMAKE_STATIC_LIBRARY_SUFFIX}
  INTERFACE_INCLUDE_DIRECTORIES ${child_ROOT}/include)

add_executable(test_timestwo test_timestwo.f90)  # your program
add_dependencies(test_timestwo CHILD)  # externalproject won't download without this
target_link_libraries(test_timestwo PRIVATE timestwo)
add_dependencies()
make ExternalProject always update and build first
CONFIGURE_HANDLED_BY_BUILD ON
tells CMake not to reconfigure each build, unless the build system requests configure
BUILD_BYPRODUCTS
necessary for Ninja to not complain about missing targets. Note how we can’t use BINARY_DIR since it’s populated by ExternalProject_Get_Property()

The imported library ext is used in the “parent” project just like any other library.


“child” project CMakeLists.txt includes:

project(child Fortran)

add_library(timestwo STATIC timestwo.f90)
set_target_properties(timestwo PROPERTIES
  Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include)

Configure “child” Fortran_MODULE_DIRECTORY so that it’s not necessary for “parent” to introspect “child” directory structure.

We have created live ExternalProject examples:

CMake can detect if a project is “top level” that is, NOT via FetchContent or ExternalProject using PROJECT_IS_TOP_LEVEL.

cmake_minimum_required(VERSION 3.21)
project(child)

if(PROJECT_IS_TOP_LEVEL)
  message(STATUS "${PROJECT_NAME} directly building, not FetchContent or ExternalProject")
endif()

Note that the PARENT_DIRECTORY property is NOT useful for detecting if the “child” is being used as an ExternalProject.


  • target_link_directories() is generally NOT preferred because library name collisions can occur, particularly with system libraries.

Reference: CMake staff comparison of multiple project with CMake

CMake version recommendations and install

CMake ≥ 3.17 is strongly recommended for general users for more robust and easy syntax. For project developers, we recommend CMake >= 3.19 as the new features make debugging CMake and setting up CI considerably easier.

Downloading the latest release of CMake is usually easy. For Linux and Mac, admin/sudo is NOT required.

There is an unoffical PyPi CMake package:

python -m pip install cmake

For platforms where CMake binaries aren’t easily available, use build_cmake.cmake.

Key features added

The priority of these features is subjective–we write from the scientific computing perspective.

CMake 3.21 adds more preset features, including making “generator” optional–the default CMake behavior will be used to determine generator. The cmake --install-prefix option can be used instead of the cumbersome cmake -DCMAKE_INSTALL_PREFIX=. PROJECT_IS_TOP_LEVEL and <PROJECT-NAME>_IS_TOP_LEVEL identify if a project is at the top of the project hierarchy. ctest --output-junit gives test output in standard tooling format.

CMake 3.20 adds support for Intel NextGen LLVM compiler and NVIDIA HPC compiler. ExternalProject_Add() learned CONFIGURE_HANDLED_BY_BUILD which avoids CMake commanding a reconfigure on each build. try_compiler(WORKING_DIRECTORY) was added. CMake presets in CMakePresets.json now covers configure, build and test, allowing many parameters to be declared with inheritance in JSON. CMake presets are a key feature for CI, as well as user configurations. ctest --test-dir build option avoids the need to manually cd build. cmake_path allows path manipulation and introspection without actually touching the filesystem.

CMake 3.19 added support for ISPC language. string(JSON GET|SET) parsing is very useful to avoid hard-coding parameters. FindPython/find_package accepts version ranges. Intel oneAPI works with CMake >= 3.19.6. Emits deprecation warning for cmake_minimum_required VERSION less than 2.8.12. CMakePresets.json enables configure parameter declarations in JSON.

CMake 3.18 adds CMake Profiler

cmake -B build --profiling-output=perf.json --profiling-format=google-trace

Adds REQUIRED parameter to find_program. Adds file(ARCHIVE_CREATE) and file(ARCHIVE_EXTRACT)

CMake 3.17 adds Ninja Multi-Config generator. cmake –debug-find shows what find_*() is doing. Eliminates Windows “sh.exe is on PATH” error. Recognizes that **Ninja 1.10 correctly works with Fortran**.

CMake 3.16 adds precompiled headers, unity builds, many advanced project features.

CMake 3.15 addes CMAKE_GENERATOR environment variable that works like global -G option. Enhances Python interpreter finding. Adds cmake --install command instead of “cmake –build build –target install”. Added Zstd compression.

CMake 3.14 is where we added check_fortran_source_runs(). FetchContent was enhanced with simpler syntax. The transitive link resolution was considerably enhanced in CMake 3.14. Projects just work in CMake >= 3.14 that fail at link-time with CMake < 3.14.


We don’t recommend use of the older CMake versions below as they take significantly more effort to support.

CMake 3.13 adds ctest --progress and better Matlab compiler support. Lots of new linking options are added, fixes to Fortran submodule bugs. The very convenient cmake -B build incantation, target_sources() with absolute path are also added. It’s significantly more difficult to use CMake older than 3.13 with medium to large projects.

CMake 3.12 adds transitive library specification (out of same directory) and full Fortran Submodule support. get_property(_test_names DIRECTORY . TESTS) retrieves test names in current directory.

CMake 3.11 allows specify targets initially w/o sources. FetchContent is added, allowing fast hierarchies of CMake and non-CMake projects.


The versions of CMake below have been deprecated as of CMake 3.19.

CMake 3.10 added Fortran Flang (LLVM) compiler and extensive MPI features.

CMake 3.9 added further C# and Cuda support, that was originally added in CMake 3.8.

CMake 3.8 added initial Cuda support

CMake 3.7 added comparing ≤ ≥ and version comparisons. Initial Fortran submodule support was added.

CMake 3.6 gave better OpenBLAS support.

CMake 3.5 enhanced FindBoost target with auto Boost prereqs.

CMake 3.4 added if(TEST) to see if a test name exists.

CMake 3.3 added list operations such as IN_LIST.

Diagnose CTest failures from logs

CTest automatically logs test outputs to:

${CMAKE_BINARY_DIR}/Testing/Temporary/LastTest.log

If you have a test failure and want to diagnose, first copy this file somewhere else to work with it, in case it gets overwritten. This file is usually quite useful with nice formatting even when running many tests in parallel.

A simple list of all “failed” and “not run” tests are in:

${CMAKE_BINARY_DIR}/Testing/Temporary/LastTestsFailed.log

“Not run” tests are those that have FIXTURES_REQUIRED that itself failed or did not run.

At the time of running CTest, one can also use the -O option like:

ctest -O test.log

“ctest -O” only logs what is printed to the screen during the CTest run. If the “ctest -V” option wasn’t used, the extra useful information as in LastTest.log such as the command line run will be missing in “test.log”.

CTest set environment variable

It’s often useful to set per-test environment variables in CMake’s CTest testing frontend. The environment variables appear and disappear with the start and end of each test, in isolation from any other tests that may be running in parallel. This is accomplished via the test property ENVIRONMENT.

Example: set environment variable FOO=1 for a test “bar” like:

set_tests_properties(bar PROPERTIES ENVIRONMENT "FOO=1")

multiple variables are set with a CMake list (semicolon delimited) like:

set_tests_properties(bar PROPERTIES ENVIRONMENT "FOO=1;BAZ=0")

Here comes an issue. In general, Windows needs DLLs to be on the current working directory or in environment variable PATH. Since Windows also delimits with a semicolon, we need to do a little extra work to append to PATH on Windows for CTest. We handle this by a script that appends to PATH for CTest on Windows:


In Python likewise set/unset environment variables within tests using PyTest monkeypatch fixture.