marp | math | theme | paginate | footer |
---|---|---|---|---|
true |
katex |
custom-theme |
true |
- Build process with CMake
- CMake Variables
- Targets and their properties
- Example CMake project
📺 Watch the related YouTube video!
- 🎨 - Style recommendation
- 🎓 - Software design recommendation
- 😱 - Not a good practice! Avoid in real life!
- ✅ - Good practice!
- ❌ - Whatever is marked with this is wrong
- 🚨 - Alert! Important information!
- 💡 - Hint or a useful exercise
- 🔼1️⃣7️⃣ - Holds for this version of C++(here,
17
) and above - 🔽1️⃣1️⃣ - Holds for versions until this one C++(here,
11
)
Style (🎨) and software design (🎓) recommendations mostly come from Google Style Sheet and the CppCoreGuidelines
Use CMake to simplify the build
- A very popular build configuration and generation tool
- Designed to solve most of the
make
shortcomings - Very powerful, while the build script is readable
- Does not build the code, generates files for a build system
- By default generates a
Makefile
with build commands - 🚨 We still need to call
make
to build the code! - 💡 Have a look at the
Makefile
generated by CMake - The generated build file is complex but does many things
- Can generate files for other build systems too (e.g.
ninja
)
- Provides access to your host system
- Configures variables for your build
- Generates the correct commands for the compiler:
- Defines library targets and executable targets
- Specifies their dependency graph
- Specifies correct build and linking flags for them
- Stores these commands in a single generated file
- The build process is fully defined in
CMakeLists.txt
files - 🚨 CMake reads each
CMakeLists.txt
sequentially - Has all the features of a scripting language, i.e., functions, control structures, variables, etc.
- Has some CMake-specific quirks 🤷♀️
- We will learn most by example
- Variables and CMake cache are especially confusing
Consists of a single file in some project_folder
:
CMakeLists.txt
cmake_minimum_required(VERSION 3.16..3.24)
project(our_project VERSION 0.0.1
DESCRIPTION "Our first project"
LANGUAGES CXX)
- Which version of CMake do we support (
3.16
---3.24
) - Name of our project:
our_project
- Version of the project:
0.0.1
- Description of what this project is about
- Languages used in this project
(Options:
C
,CXX
,CUDA
,OBJC
,OBJCXX
,Fortran
, etc.)
✅ Configure and generate using a command line:
cd <project_folder> # One that has CMakeLists.txt in it
cmake -S . -B build # Configure build and generate a Makefile
cmake --build build -j 8 # Run make with 8 threads
✅ Configure and generate using TUI (text user interface)
cd <project_folder> # One that has CMakeLists.txt in it
ccmake -S . -B build # Press "c" to configure and "g" to generate
cmake --build build -j 8 # Run make with 8 threads
😱 If you only have an older CMake version (🔽3.13
)
cd <project_folder>
mkdir build
cd build
cmake .. # Configure build and generate a Makefile
make -j8 # Run make with 8 threads
- 🚨 Always build using the
build
sub-folder!
- CMake maintains a cache of variables
(look for
CMakeCache.txt
file after running CMake) - Two types of CMake variables: cached and scope-local
# Local variable hides any cached variable with the same name set(VAR "value") # Does not get copied to cache! # Cached variable only set if not found already in cache set(VAR "value" CACHE STRING "Description") # Ignore the value in the cache and always set variable value set(VAR "value" CACHE STRING "" FORCE) set(VAR "value" CACHE INTERNAL "")
- Use
option
(ON
orOFF
) for a boolean cached variableoption(CUSTOM_OPTION "Description" OFF)
- Use the value of a variable as
${VAR}
- We can add a variable to cache from command line:
cmake -D FOO="foo" -D BAR="bar" -S . -B build
- Show a simple message:
message(STATUS "My awesome message")
- Use variables in messages:
set(FOO 42) # A local variable message(STATUS "Value of FOO variable is: ${FOO}") # The PROJECT_NAME variable is defined by project(...) command message(STATUS "Project name is: ${PROJECT_NAME}")
- Show warnings and errors with
WARNING
,FATAL_ERROR
if(NOT CMAKE_BUILD_TYPE) message(WARNING "CMAKE_BUILD_TYPE is not defined!") endif() if(NOT FOO) message(FATAL_ERROR "FOO is not defined!") endif()
- Omit the message type to always print the message:
message("Always print this")
- To add a shared library do:
add_library(my_library SHARED ${PROJECT_SOURCE_DIR}/folder/source_1.cpp source_2.cpp)
- To add a static library do:
add_library(my_library STATIC ${PROJECT_SOURCE_DIR}/folder/source_1.cpp source_2.cpp)
- Use
INTERFACE
for header-only librariesadd_library(my_library INTERFACE) # 🚨 No source files needed!
- 💡 Note that source file paths are relative to current folder
- 💡 For absolute paths use
${PROJECT_SOURCE_DIR}
- 💡 If no
STATIC
orSHARED
are provided the value of theBUILD_SHARED_LIBS
variable defines the library type
- Very similar to the
add_library
command - To add an executable do:
add_executable(my_executable source_with_main.cpp)
🚨 Note that there must be amain
function in the source file
- Modern CMake records properties per-target
- Property visibilities:
PRIVATE
,PUBLIC
orINTERFACE
- Targets created as
INTERFACE
(e.g. header-only libraries) can only have properties withINTERFACE
visibility - 🚨 For a target
my_target
and a command that sets its propertiescommand(my_target <VISIBILITY> properties)
<VISIBILITY> |
properties seen in my_target |
properties seen in targets depending on my_target |
---|---|---|
PUBLIC |
✅ | ✅ |
PRIVATE |
✅ | ❌ |
INTERFACE |
❌ | ✅ |
- Use
target_link_libraries
to express dependencies between library and binary targets:target_link_libraries(my_target PUBLIC|PRIVATE|INTERFACE lib)
- 💡
my_target
can be either a library or an executable - 🚨 Confusion point:
target_link_libraries
doesn't just "link" libraries! It also propagates all visible (PUBLIC
andINTERFACE
) properties oflib
tomy_target
- They map to what we care about when compiling
- Set compilation flags for using
#include
:-I
, etc.
✅ Usetarget_include_directories
command - Set linker flags:
-L
,-l
:
✅ Usetarget_link_libraries
command - Set flags to choose the C++ standard:
-std=c++17
✅ Usetarget_compile_features
- Other compilation flags:
-Wall
,-Wextra
etc.
✅ Usetarget_compile_options
- All of these use the same interface and visibility
- Use
PRIVATE
properties withadd_executable
targets - Mostly use
PUBLIC
properties on library targets - Use
INTERFACE
properties on header-only library targets - 🚨 Always specify visibility with commands:
target_include_directories
target_compile_options
target_compile_features
target_link_libraries
- (generally any command that starts with
target_
)
Typical project structure
our_project/
├── CMakeLists.txt
├── executables # Can also be "examples" or alike
│ ├── CMakeLists.txt
│ └── print_hello.cpp
├── our_project/ # Name of this project (again)
│ ├── CMakeLists.txt
│ └── tools/ # A library (typically more than one)
│ ├── CMakeLists.txt
│ ├── tools.cpp
│ ├── tools.h
│ └── tools_test.cpp # 💡 Can be in a separate folder too
├── build/ # All generated build files. Don't modify manually!
├── .git # Generated by git. Don't modify manually!
├── .clang-format
├── .gitignore
└── readme.md # Description of your project. Very important!
Let's fill in all (well, most) of these files!
Stay tuned for the *_test.cpp
files in a later video 😉
🎨 It's a good style if namespaces follow the folder structure
our_project/our_project/tools/tools.h
#pragma once namespace our_project { namespace tools { void PrintHello(); }} // Saving place on slides here, use clang-format!
our_project/our_project/tools/tools.cpp
#include <our_project/tools/tools.h> #include <iostream> namespace our_project { namespace tools { void PrintHello() { std::cout << "Hello!" << std::endl; } }} // Saving place on slides here, use clang-format!
our_project/executables/print_hello.cpp
#include <our_project/tools/tools.h> int main() { our_project::tools::PrintHello(); return 0; }
our_project/CMakeLists.txt
cmake_minimum_required(VERSION 3.16..3.24)
project(our_project VERSION 0.0.1
DESCRIPTION "Our first project"
LANGUAGES CXX)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
add_library(cxx_setup INTERFACE)
target_compile_options(cxx_setup
INTERFACE -Wall -Wpedantic -Wextra)
target_compile_features(cxx_setup INTERFACE cxx_std_17)
target_include_directories(cxx_setup
INTERFACE ${PROJECT_SOURCE_DIR})
# 🚨 Make sure CMakeLists.txt file exists in the subdirectories!
add_subdirectory(${PROJECT_NAME})
add_subdirectory(executables)
# 💡 Some things (e.g. tests or installation) are missing here.
# 💡 Stay tuned for these in future lectures!
our_project/our_project/CMakeLists.txt
add_subdirectory(tools)
our_project/our_project/tools/CMakeLists.txt
add_library(tools tools.cpp) target_link_libraries(tools PUBLIC cxx_setup)
our_project/executables/CMakeLists.txt
add_executable(print_hello print_hello.cpp) target_link_libraries(print_hello PRIVATE tools cxx_setup)
We have to execute the following commands: (with approximately the same output)
cd path/to/our/project/our_project
λ › cmake -S . -B build
-- The CXX compiler identification is AppleClang 14.0.0.14000029
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: .../usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: .../our_project/build
λ › cmake --build build
[ 25%] Building CXX object .../tools.cpp.o
[ 50%] Linking CXX static library libtools.a
[ 50%] Built target tools
[ 75%] Building CXX object .../print_hello.cpp.o
[100%] Linking CXX executable print_hello
[100%] Built target print_hello
- To "clean" means to remove all generated build artifacts
- That includes all libraries and binaries as well as all the temporary files generated by the build system
- It's very easy to do with CMake