CMake targets and libraries
A CMake target is a library or an executable plus the properties it needs: sources, include directories, compile features, options and libraries it links. The properties travel with the target, so a consumer does not have to repeat them.
This is the difference from the file-level build of lesson 098: the dependency is expressed once, on the target that owns it. This lesson builds a small math library and an executable that links it, one property at a time.
The archive is named libmath.a in the build tree, and the executable links it by that name. A target that is exported installs both the archive and its interface, which is how a library is consumed by another project.
A target that installs its headers and its archive can be consumed by another project with find_package, which is lesson 103. The properties set here are exactly what that consumer receives.
A library target
add_library(math STATIC math.cpp) builds a static library from one source. The executable then names it in target_link_libraries and gets the objects at link time, without copying them into the project.
The program's main calls square from the library. The configure step writes the rules, the build compiles the library and the executable, and the run prints 49.
A library target can be STATIC, SHARED or omitted, in which case the value of BUILD_SHARED_LIBS decides. A project that does not care lets the packager choose.
square.h
#pragma once
int square(int value);
math.cpp
#include "square.h"
int square(int value)
{
return value * value;
}
main.cpp
#include <iostream>
#include "square.h"
int main()
{
std::cout << square(7) << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(toolbox CXX)
add_library(math STATIC math.cpp)
add_executable(toolbox main.cpp)
target_link_libraries(toolbox PRIVATE math)
cmake -S . -B build
cmake --build build
./build/toolbox
-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/rebuild/100_cmake_targets/s0/build
[ 25%] Building CXX object CMakeFiles/math.dir/math.cpp.o
[ 50%] Linking CXX static library libmath.a
[ 50%] Built target math
[ 75%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
[100%] Linking CXX executable toolbox
[100%] Built target toolbox
49
Include directories
The header is in the source directory, which happens to be the include path of the compiler by default. A library in a subdirectory needs target_include_directories so its consumers find the header.
The program moves square.h into include/, sets the directory on the library with the PUBLIC keyword, and main.cpp includes it by name. The executable inherits the path through the link.
PUBLIC means the directory is needed by the target and by everyone who links it; PRIVATE means only by the target itself. Getting that wrong is the usual reason a consumer cannot find a header.
include/square.h
#pragma once
int square(int value);
math.cpp
#include "square.h"
int square(int value)
{
return value * value;
}
main.cpp
#include <iostream>
#include "square.h"
int main()
{
std::cout << square(8) << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(toolbox CXX)
add_library(math STATIC math.cpp)
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_executable(toolbox main.cpp)
target_link_libraries(toolbox PRIVATE math)
cmake -S . -B build
cmake --build build
./build/toolbox
-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/rebuild/100_cmake_targets/s1/build
[ 25%] Building CXX object CMakeFiles/math.dir/math.cpp.o
[ 50%] Linking CXX static library libmath.a
[ 50%] Built target math
[ 75%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
[100%] Linking CXX executable toolbox
[100%] Built target toolbox
64
Compile features
target_compile_features(math PUBLIC cxx_std_23) asks for the C++23 standard without naming a flag. CMake translates it to -std=gnu++23 or the equivalent of the compiler it found.
The program sets the feature on the library and lets the executable inherit it through the link. The build works on a compiler whose flag spelling is different, which is the point of the feature names.
The older form sets CMAKE_CXX_STANDARD globally, as the previous lesson did. A feature on the target is the modern form because it travels with the target.
square.h
#pragma once
int square(int value);
math.cpp
#include "square.h"
int square(int value)
{
return value * value;
}
main.cpp
#include <iostream>
#include "square.h"
int main()
{
auto cube = [](int value) { return value * value * value; };
std::cout << square(3) << ' ' << cube(3) << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(toolbox CXX)
add_library(math STATIC math.cpp)
target_compile_features(math PUBLIC cxx_std_23)
add_executable(toolbox main.cpp)
target_link_libraries(toolbox PRIVATE math)
cmake -S . -B build
cmake --build build --verbose
./build/toolbox
-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/rebuild/100_cmake_targets/s2/build
Change Dir: '/tmp/rebuild/100_cmake_targets/s2/build'
Run Build Command(s): /usr/bin/cmake -E env VERBOSE=1 /usr/bin/gmake -f Makefile
/usr/bin/cmake -S/tmp/rebuild/100_cmake_targets/s2 -B/tmp/rebuild/100_cmake_targets/s2/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /tmp/rebuild/100_cmake_targets/s2/build/CMakeFiles /tmp/rebuild/100_cmake_targets/s2/build//CMakeFiles/progress.marks
/usr/bin/gmake -f CMakeFiles/Makefile2 all
gmake[1]: Entering directory '/tmp/rebuild/100_cmake_targets/s2/build'
/usr/bin/gmake -f CMakeFiles/math.dir/build.make CMakeFiles/math.dir/depend
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s2/build'
cd /tmp/rebuild/100_cmake_targets/s2/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /tmp/rebuild/100_cmake_targets/s2 /tmp/rebuild/100_cmake_targets/s2 /tmp/rebuild/100_cmake_targets/s2/build /tmp/rebuild/100_cmake_targets/s2/build /tmp/rebuild/100_cmake_targets/s2/build/CMakeFiles/math.dir/DependInfo.cmake "--color=" math
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s2/build'
/usr/bin/gmake -f CMakeFiles/math.dir/build.make CMakeFiles/math.dir/build
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s2/build'
[ 25%] Building CXX object CMakeFiles/math.dir/math.cpp.o
/usr/bin/c++ -std=gnu++23 -MD -MT CMakeFiles/math.dir/math.cpp.o -MF CMakeFiles/math.dir/math.cpp.o.d -o CMakeFiles/math.dir/math.cpp.o -c /tmp/rebuild/100_cmake_targets/s2/math.cpp
[ 50%] Linking CXX static library libmath.a
/usr/bin/cmake -P CMakeFiles/math.dir/cmake_clean_target.cmake
/usr/bin/cmake -E cmake_link_script CMakeFiles/math.dir/link.txt --verbose=1
/usr/bin/ar qc libmath.a CMakeFiles/math.dir/math.cpp.o
/usr/bin/ranlib libmath.a
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s2/build'
[ 50%] Built target math
/usr/bin/gmake -f CMakeFiles/toolbox.dir/build.make CMakeFiles/toolbox.dir/depend
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s2/build'
cd /tmp/rebuild/100_cmake_targets/s2/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /tmp/rebuild/100_cmake_targets/s2 /tmp/rebuild/100_cmake_targets/s2 /tmp/rebuild/100_cmake_targets/s2/build /tmp/rebuild/100_cmake_targets/s2/build /tmp/rebuild/100_cmake_targets/s2/build/CMakeFiles/toolbox.dir/DependInfo.cmake "--color=" toolbox
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s2/build'
/usr/bin/gmake -f CMakeFiles/toolbox.dir/build.make CMakeFiles/toolbox.dir/build
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s2/build'
[ 75%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
/usr/bin/c++ -std=gnu++23 -MD -MT CMakeFiles/toolbox.dir/main.cpp.o -MF CMakeFiles/toolbox.dir/main.cpp.o.d -o CMakeFiles/toolbox.dir/main.cpp.o -c /tmp/rebuild/100_cmake_targets/s2/main.cpp
[100%] Linking CXX executable toolbox
/usr/bin/cmake -E cmake_link_script CMakeFiles/toolbox.dir/link.txt --verbose=1
/usr/bin/c++ -Wl,--dependency-file=CMakeFiles/toolbox.dir/link.d CMakeFiles/toolbox.dir/main.cpp.o -o toolbox libmath.a
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s2/build'
[100%] Built target toolbox
gmake[1]: Leaving directory '/tmp/rebuild/100_cmake_targets/s2/build'
/usr/bin/cmake -E cmake_progress_start /tmp/rebuild/100_cmake_targets/s2/build/CMakeFiles 0
9 27
Compile options
target_compile_options adds flags the target needs, such as -Wall -Wextra. The flags are compiler-specific, so a portable project guards them by compiler or uses a generator expression.
The program sets the warnings on the library and on the executable and builds. The verbose build shows the g++ command with the two flags in it, which is how a flag is verified to have arrived.
Options are the escape hatch for what features cannot express: sanitizers, optimisation level, a macro. Everything about the target belongs on the target, and a consumer gets it for free.
square.h
#pragma once
int square(int value);
math.cpp
#include "square.h"
int square(int value)
{
return value * value;
}
main.cpp
#include <iostream>
#include "square.h"
int main()
{
std::cout << square(5) << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(toolbox CXX)
add_library(math STATIC math.cpp)
target_compile_options(math PRIVATE -Wall -Wextra)
add_executable(toolbox main.cpp)
target_link_libraries(toolbox PRIVATE math)
target_compile_options(toolbox PRIVATE -Wall -Wextra)
cmake -S . -B build
cmake --build build --verbose
./build/toolbox
-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/rebuild/100_cmake_targets/s3/build
Change Dir: '/tmp/rebuild/100_cmake_targets/s3/build'
Run Build Command(s): /usr/bin/cmake -E env VERBOSE=1 /usr/bin/gmake -f Makefile
/usr/bin/cmake -S/tmp/rebuild/100_cmake_targets/s3 -B/tmp/rebuild/100_cmake_targets/s3/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /tmp/rebuild/100_cmake_targets/s3/build/CMakeFiles /tmp/rebuild/100_cmake_targets/s3/build//CMakeFiles/progress.marks
/usr/bin/gmake -f CMakeFiles/Makefile2 all
gmake[1]: Entering directory '/tmp/rebuild/100_cmake_targets/s3/build'
/usr/bin/gmake -f CMakeFiles/math.dir/build.make CMakeFiles/math.dir/depend
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s3/build'
cd /tmp/rebuild/100_cmake_targets/s3/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /tmp/rebuild/100_cmake_targets/s3 /tmp/rebuild/100_cmake_targets/s3 /tmp/rebuild/100_cmake_targets/s3/build /tmp/rebuild/100_cmake_targets/s3/build /tmp/rebuild/100_cmake_targets/s3/build/CMakeFiles/math.dir/DependInfo.cmake "--color=" math
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s3/build'
/usr/bin/gmake -f CMakeFiles/math.dir/build.make CMakeFiles/math.dir/build
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s3/build'
[ 25%] Building CXX object CMakeFiles/math.dir/math.cpp.o
/usr/bin/c++ -Wall -Wextra -MD -MT CMakeFiles/math.dir/math.cpp.o -MF CMakeFiles/math.dir/math.cpp.o.d -o CMakeFiles/math.dir/math.cpp.o -c /tmp/rebuild/100_cmake_targets/s3/math.cpp
[ 50%] Linking CXX static library libmath.a
/usr/bin/cmake -P CMakeFiles/math.dir/cmake_clean_target.cmake
/usr/bin/cmake -E cmake_link_script CMakeFiles/math.dir/link.txt --verbose=1
/usr/bin/ar qc libmath.a CMakeFiles/math.dir/math.cpp.o
/usr/bin/ranlib libmath.a
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s3/build'
[ 50%] Built target math
/usr/bin/gmake -f CMakeFiles/toolbox.dir/build.make CMakeFiles/toolbox.dir/depend
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s3/build'
cd /tmp/rebuild/100_cmake_targets/s3/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /tmp/rebuild/100_cmake_targets/s3 /tmp/rebuild/100_cmake_targets/s3 /tmp/rebuild/100_cmake_targets/s3/build /tmp/rebuild/100_cmake_targets/s3/build /tmp/rebuild/100_cmake_targets/s3/build/CMakeFiles/toolbox.dir/DependInfo.cmake "--color=" toolbox
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s3/build'
/usr/bin/gmake -f CMakeFiles/toolbox.dir/build.make CMakeFiles/toolbox.dir/build
gmake[2]: Entering directory '/tmp/rebuild/100_cmake_targets/s3/build'
[ 75%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
/usr/bin/c++ -Wall -Wextra -MD -MT CMakeFiles/toolbox.dir/main.cpp.o -MF CMakeFiles/toolbox.dir/main.cpp.o.d -o CMakeFiles/toolbox.dir/main.cpp.o -c /tmp/rebuild/100_cmake_targets/s3/main.cpp
[100%] Linking CXX executable toolbox
/usr/bin/cmake -E cmake_link_script CMakeFiles/toolbox.dir/link.txt --verbose=1
/usr/bin/c++ -Wl,--dependency-file=CMakeFiles/toolbox.dir/link.d CMakeFiles/toolbox.dir/main.cpp.o -o toolbox libmath.a
gmake[2]: Leaving directory '/tmp/rebuild/100_cmake_targets/s3/build'
[100%] Built target toolbox
gmake[1]: Leaving directory '/tmp/rebuild/100_cmake_targets/s3/build'
/usr/bin/cmake -E cmake_progress_start /tmp/rebuild/100_cmake_targets/s3/build/CMakeFiles 0
25
The header that is not on the path
A library whose header is in a subdirectory but which does not publish that directory leaves its consumers unable to find the header. The error comes from the compiler, not from CMake, and it appears at build time.
The program puts square.h in include/ and omits target_include_directories. The compile of main.cpp stops at No such file or directory, which is the message of lesson 006 in a new place.
The fix is the PUBLIC include directory on the library. A PRIVATE one would let the library compile and still leave the consumer broken, which is the subtler version of the same bug.
include/square.h
#pragma once
int square(int value);
math.cpp
#include "square.h"
int square(int value)
{
return value * value;
}
main.cpp
#include <iostream>
#include "square.h"
int main()
{
std::cout << square(5) << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(toolbox CXX)
add_library(math STATIC math.cpp)
target_include_directories(math PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_executable(toolbox main.cpp)
target_link_libraries(toolbox PRIVATE math)
cmake -S . -B build
cmake --build build
-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/rebuild/100_cmake_targets/s4/build
[ 25%] Building CXX object CMakeFiles/math.dir/math.cpp.o
[ 50%] Linking CXX static library libmath.a
[ 50%] Built target math
[ 75%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
/tmp/rebuild/100_cmake_targets/s4/main.cpp:3:10: fatal error: square.h: No such file or directory
3 | #include "square.h"
| ^~~~~~~~~~
compilation terminated.
gmake[2]: *** [CMakeFiles/toolbox.dir/build.make:79: CMakeFiles/toolbox.dir/main.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:122: CMakeFiles/toolbox.dir/all] Error 2
gmake: *** [Makefile:91: all] Error 2
Exercises
- Add a second library target and link both into the executable.
- Turn the library into a shared one by setting BUILD_SHARED_LIBS and check the binary type.
- Add a target_compile_definition that sets a version macro and print it from main.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Which command creates a static library target called math?
Answer
add_library(math STATIC math.cpp)add_library creates a library; STATIC chooses the archive form.
-
Which command makes an executable depend on that library?
Answer
target_link_libraries(toolbox PRIVATE math)target_link_libraries records the dependency and passes it to the linker.
-
What does the PUBLIC keyword on target_include_directories mean?
Answer
The directory is needed by this target and by everything that links it
PUBLIC exports the property along the dependency edge; PRIVATE keeps it local.
-
What does
target_compile_features(math PUBLIC cxx_std_23)do?Answer
Asks for the C++23 standard in the compiler's own spelling
The feature name is portable; CMake translates it to the flag of the compiler it found.
-
What does this CMake code build?
add_library(m STATIC a.cpp)andadd_executable(e b.cpp)andtarget_link_libraries(e PRIVATE m)Answer
A library and an executable linked against it
Two targets, and the executable links the archive.
-
Where does the linker find the library in the lesson's build?
Answer
In the build tree, as a generated archive
The static library is built into the build directory and the link line points there.
-
What happens when a consumer includes square.h and the library published the include directory as PRIVATE?
Answer
The compile of the consumer fails with a missing header
PRIVATE does not export the path, so only the library's own sources see it.
-
Which command adds -Wall to a target?
Answer
target_compile_options(math PRIVATE -Wall)target_compile_options is the per-target way to add flags.
-
What does the verbose build show?
Answer
The exact compiler and linker commands CMake generated
--verbose prints each command, which is how a flag or a path is verified.
-
Why do properties belong to targets rather than to global variables?
Answer
They travel with the target to every consumer
A target carries its include paths, features and options to whoever links it, without repeating them.
The code on this page was built with g++ 15.2.0 and CMake 4.2.3 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev