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

  1. Add a second library target and link both into the executable.
  2. Turn the library into a shared one by setting BUILD_SHARED_LIBS and check the binary type.
  3. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. What does this CMake code build? add_library(m STATIC a.cpp) and add_executable(e b.cpp) and target_link_libraries(e PRIVATE m)

    Answer

    A library and an executable linked against it

    Two targets, and the executable links the archive.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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