CMake Intro

James Smith
Jun. 15th, 2023

Credits
Two great resources by Henry Schreiner that are the basis for this talk:
Put simply, a build system is a set of programs and companion text files that collectively build a software code base.

- Onorato Vaticone

Popular build systems

For a more complete discussion see: hacking C++ blog.

*Technically, CMake is a build system generator, but I'll use the terms interchangably here.

Objectives
  1. Use/understand someone else's CMake infrastructure
  2. Write a simple CMake build system from scratch
  3. Integrate tests with CMake/CTest
  4. [Next time]: Look for libraries/packages
  5. [Next time]: Manage dependencies
  6. [Next time]: Install packages with CMake

We'll be doing a side-by-side presentation from now, check out the examples:

https://github.com/jamesETsmith/2023-06-15-cmake-intro

Part 1: Building someone else's CMake project

Goals:
  1. Configure CMake project
  2. Build CMake project
  3. Modify configure/build behavior from the command line
Configure + Build

					cmake -S . -B build
					cmake --build build
					cmake --build build --target test
				

Old school procedure (avoid if possible)


					mkdir -p build
					cd build
					cmake ..
					make
					make test
				

Use build directory, don't do in-source builds

Basic Command Line Options

Verbose builds


					cmake --build build --verbose # Option 1
					cd build && VERBOSE=1 make    # Option 2
				

Setting options (-DOPTION)


					cmake -S . -B build -DCMAKE_CXX_COMPILER=clang++
					cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
					cmake -S . -B build -DBUILD_SHARED_LIBS=ON
					cmake -S . -B build -DCMAKE_INSTALL_PREFIX=$(pwd)/install
				
Basic Debugging
  • Inspect the result of the configuration phase!
  • Build with --verbose flag
  • List some CMake options/variables variables with
    cmake -LH
  • See all cached options/variables in
    vim build/CMakeCache.txt

Part 2: Writing your own CMake

Goals:
  1. Write simple CMake project from scratch
  2. Use CMake to build an executable
  3. Use CMake to build a library
The Bare Minimum

02-simple-exec/CMakeLists.txt


						cmake_minimum_required(VERSION 3.20)

						project(project_name)

						add_executable(simple_target simple.cpp)
				
First Line

					cmake_minimum_required(VERSION 3.20)
				

  • Sets the "policies" so the build behaves exactly like the specified version
  • Makes CMake backward compatible until support is dropped
  • Think of this as CMake "dumbing itself down"

See "More Modern CMake" for details

Project Details
						
						project(project_name)
						
					

						project(
							project_name
							VERSION 1.0
							DESCRIPTION "Very nice project"
							LANGUAGES CXX)	
					

  • project sets the project name and can optionally specify the name, version, description, and languages.
  • CMake assumes both C and C++ (called CXX inside CMake)
Building an Executable

					add_executable(simple_target simple.cpp)
				

  • First argument is the target name (must be unique)
  • Second argument is the set of source files
  • Typically have at least one executable (add_exectuable) or library (add_library)
Building a Library

					add_library(simple_lib STATIC simple.cpp)
				

  • First argument is the target name (must be unique)
  • Second argument can be one of the following:
    • STATIC
    • SHARED
    • INTERFACE (fictional target with no compilation)
    • ALIAS (alias for existing target)
    • MODULE (libraries loaded dynamically at runtime)
Building an Application

02-simple-app/CMakeLists.txt


					cmake_minimum_required(VERSION 3.20)

					project(simple_app)

					add_library(simple_lib STATIC simple_lib.cpp)
					target_include_directories(simple_lib PUBLIC include)

					add_executable(simple_app simple_app.cpp)
					target_link_libraries(simple_app PUBLIC simple_lib)
				

  • PUBLIC: Used by current target and dependencies
  • PRIVATE: Used by just current target
  • INTERFACE: Used by just dependencies

Key points:

  • You only need three things for a CMake build system: cmake_minimum_required, project, and add_library
  • Think in terms of targets

Part 3: Integrating tests with CMake/CTest

Goals
  • Build tests
  • Run tests
  • Add tests
Building tests
  • Not all projects build tests by default
  • Some use -DBUILD_TESTING=ON
  • If they use a custom flag, look in their CMakeLists.txt for
    • include(CTest)
    • enable_testing()
    • add_subdirectory
Running Tests

					# Run serially
					cmake --build build --target test
					ctest --test-dir build
					cmake --build build --target test --parallel 12
					# Run in parallel
					CTEST_PARALLEL_LEVEL=12 cmake --build build --target test
					ctest --test-dir build --parallel 12
					# Select subset of tests with RegEx
					ctest --test-dir build -R filter
				
Project Structure

In the next few examples, the directory structure will start to look more realistic, i.e.:

.
├── cmake
│   └── FindProject.cmake
├── CMakeLists.txt
├── include
│   └── project
│       └── lib.hpp
├── src
│   ├── CMakeLists.txt
│   └── lib.cpp
└── test
    ├── CMakeLists.txt
    ├── test_1.cpp
    └── test_2.cpp
				
Test Example

03-test-simple/CMakeLists.txt


				cmake_minimum_required(VERSION 3.20)

				project(simple_lib)

				# src/CMakeLists.txt creates library target
				add_subdirectory(src)

				enable_testing()
				# test/CMakeLists.txt creates test targets and registers them
				add_subdirectory(test)
					
Test Example

03-test-simple/src/CMakeLists.txt


					add_library(simple_lib STATIC simple_lib.cpp)
					target_include_directories(simple_lib PUBLIC ${CMAKE_SOURCE_DIR}/include)
					
Test Example

03-test-simple/test/CMakeLists.txt


					add_executable(test_1 ${CMAKE_CURRENT_SOURCE_DIR}/test_1.cpp)
					target_link_libraries(test_1 PUBLIC simple_lib)
					add_test(NAME test_1_fancy_name COMMAND test_1)
					
					# This test is written to fail
					add_executable(test_2 ${CMAKE_CURRENT_SOURCE_DIR}/test_2.cpp)
					target_link_libraries(test_2 PUBLIC simple_lib)
					add_test(NAME test_2_fancy_name COMMAND test_2)
					
					
Part 3: Miscellaneous Tips
Antipatterns
  • Don't use global functions like link_directories or link_libraries
  • Don't add PUBLIC requirements where it's not needed
  • Don't link to build library files, link to their targets
Setting Compiler Flags

					cmake -S . -B build -DCMAKE_C_FLAGS="-O2 -ftree-vectorize"
				

Note that these flags may interact with those set by CMAKE_BUILD_TYPE

Questions?