Brief tour to demonstrate concepts/capabilities
Objectives
Get a feel for how CMake looks.
See 2-3 use cases where CMake shines (environment/library discovery, code generation, testing, modular code structure, Fortran 90 support).
The goal here is not to focus on syntax or details but rather the big picture and the potential.
Note
To follow-along in your terminal you can clone or download the tutorial repository (https://github.com/coderefinery/cmake-workshop):
$ git clone https://github.com/coderefinery/cmake-workshop.git
You find the example in the content/examples/calculator/ folder.
We will discuss an example project “calculator” which can add and subtract and is a mixed Fortran-C++ project with the following layout:
calculator/
├── CMakeLists.txt
├── src
│ ├── add.f90
│ ├── calculator.h
│ ├── CMakeLists.txt
│ ├── git-version.cmake
│ ├── main.cpp
│ ├── subtract.f90
│ └── version-template.h
└── test
├── calculator.cpp
├── CMakeLists.txt
└── driver.cpp
Before we have a closer look we open up the top-level
CMakeLists.txt
and for the moment comment out the part with the testing. We will bring it back later:1# stop if CMake version is below 3.0 2cmake_minimum_required(VERSION 3.0 FATAL_ERROR) 3 4# project name and supported languages 5project(example VERSION 1.0.0 LANGUAGES CXX Fortran) 6 7# require C++11 8set(CMAKE_CXX_STANDARD 11) 9set(CMAKE_CXX_EXTENSIONS OFF) 10set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 12# specify where to place binaries and libraries 13# so they end up in standard places with "make install" 14include(GNUInstallDirs) 15set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 16set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 17set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) 18 19# process src/CMakeLists.txt 20add_subdirectory(src) 21 22# enable testing 23enable_testing() 24 25# process test/CMakeLists.txt 26add_subdirectory(test)
Now we build the project and observe what it means to build out of source.
$ cmake -S. -Bbuild $ cmake --build build
But many people (especially CMake old-timers like the presenter) do this instead with Unix Makefiles:
$ mkdir build $ cd build $ cmake .. $ make
We also run the compiled binary:
$ ./bin/calculator.x Running calculator version v1.0.0 Configure-time Git hash: fca3425 2 + 3 = 5 2 - 3 = -1
We browse and discuss the main
CMakeLists.txt
and alsosrc/CMakeLists.txt
.We discuss how the Git hash ends up in the generated
version.h
with the help of:1# in case Git is not available, we default to "unknown" 2set(GIT_HASH "unknown") 3 4# find Git and if available set GIT_HASH variable 5find_package(Git QUIET) 6 7if(GIT_FOUND) 8 execute_process( 9 COMMAND ${GIT_EXECUTABLE} --no-pager show -s --pretty=format:%h -n 1 10 OUTPUT_VARIABLE GIT_HASH 11 OUTPUT_STRIP_TRAILING_WHITESPACE 12 WORKING_DIRECTORY 13 ${PROJECT_SOURCE_DIR} 14 ERROR_QUIET 15 ) 16endif()
Then we re-activate the testing, reconfigure from scratch (this is not needed but it shows the moment better when the external library is downloaded):
1# enable FetchContent 2include(FetchContent) 3 4# declare where to find the dependency 5FetchContent_Declare(GoogleTest 6 GIT_REPOSITORY https://github.com/google/googletest.git 7 GIT_TAG release-1.11.0 8 ) 9 10# bring the dependency into scope 11FetchContent_MakeAvailable(GoogleTest) 12 13# build and link the unit_tests executable 14add_executable(unit_tests) 15target_sources( 16 unit_tests 17 PRIVATE 18 calculator.cpp 19 driver.cpp 20 ) 21target_link_libraries( 22 unit_tests 23 PRIVATE 24 calculator 25 gtest_main # this target is not here, we fetch it from the web (above) 26 ) 27 28# define tests 29add_test( 30 NAME adding 31 COMMAND $<TARGET_FILE:unit_tests> --gtest_filter=calculator.add 32 ) 33add_test( 34 NAME subtracting 35 COMMAND $<TARGET_FILE:unit_tests> --gtest_filter=calculator.subtract 36 )
We build and run the test set:
$ ctest
We also run the test set (consisting of two tests) in parallel:
$ ctest -j 2
For the Fortran folks we create one more Fortran module and “use” it in
add.f90
and demonstrate that the order does not matter and CMake figures it out.We try the install target:
$ mkdir build $ cd build $ cmake -D CMAKE_INSTALL_PREFIX=/tmp/calculator-example $ make $ make test $ make install