This workshop has moved! New address:

https://hsf-training.github.io/hsf-training-cmake-webpage/04-targets/index.html

Working with Targets

Overview

Teaching: 10 min
Exercises: 15 min
Questions
  • How do targets work?

Objectives
  • Know how to set up targets

Targets

Now you know how to compile a single file using three lines of CMake. But what happens if you have more than one file with dependencies? You need to be able to tell CMake about the structure of your project, and it will help you build it. To do so, you will need targets.

You’ve already seen a target:

add_executable(myexample simple.cpp)

This creates an “executable” target with the name mytarget. Target names must be unique (and there is a way to set the executable name to something other than the target name if you really want to).

Targets are much like “objects” in other languages; they have properties (member variables) that hold information. The SOURCE_FILES property, for example, will have simple.cpp in it.

Another type of target is a library:

add_library(mylibrary simplelib.cpp)

You can add the keywords STATIC, SHARED, or MODULE if you know what kind of library you want to make; the default is sort-of an “auto” library that is user selectable with BUILD_SHARED_LIBS.

You can make non-built libraries too. More on that later, once we see what we can do with targets.

Linking

Once you have several targets, you can describe the relationship between them with target_link_libraries and a keyword; one of PUBLIC, PRIVATE, and INTERFACE. Don’t forget this keyword when making a library! CMake goes into an old compatibility mode for this target that generally breaks things.

Question

You have a library, my_lib, made from my_lib.hpp and my_lib.cpp. It requires at least C++14 to compile. If you then add my_exe, and it needs my_lib, should that force my_exe to compile with C++14 or better?

Answer

This depends on the header. If the header contains C++14, this is a PUBLIC requirement - both the library and it’s users need it. However, if the header is valid in all versions of C++, and only the implementations inside my_lib.cpp require C++14, then this is a PRIVATE requirement

  • users don’t need to be forced into C++14 mode.

Maybe you do require users have C++14, but your library can compile with any version of C++. This would be an INTERFACE requirement.

Example of Public and Private inheritance

Figure 1: Example of PUBLIC, PRIVATE, and INTERFACE. myprogram will build the three libraries it sees through mylibrary; the private library will not affect it.

There are two collections of properties on every target that can be filled with values; the “private” properties control what happens when you build that target, and the “interface” properties tell targets linked to this one what to do when building. The PUBLIC keyword fills both property fields at the same time.

Example 1: Include directories

When you run target_include_directory(TargetA PRIVATE mydir), then the INCLUDE_DIRECTORIES property of TargetA has mydir appended. If you use the keyword INTERFACE instead, then INTERFACE_INCLUDE_DIRECTORIES is appended to, instead. If you use PUBLIC, then both properties are appended to at the same time.

Example 2: C++ standard

There is a C++ standard property - CXX_STANDARD. You can set this property, and like many properties in CMake, it gets it’s default value from a CMAKE_CXX_STANDARD variable if it is set, but there is no INTERFACE version - you cannot force a CXX_STANDARD via a target. What would you do if you had a C++11 interface target and a C++14 interface target and linked to both?

By the way, there is a way to handle this - you can specify the minimum compile features you need to compile a target; the std_cxx_11 and similar meta-features are perfect for this - your target will compile with at least the highest level specified, unless CXX_STANDARD is set (and that’s a nice, clear error if you set CXX_STANDARD too low). target_compile_features can fill COMPILE_FEATURES and INTERFACE_COMPILE_FEATURES, just like directories in example 1.

Try it out

Get this repository and go to the example. Try to write a CMakeLists that will correctly build.

git clone https://github.com/henryiii/cmake_workshop.git
cd cmake_workshop/code/01-simple

The files here are:

  • simple_lib.cpp: Must be compiled with MYLIB_PRIVATE and MYLIB_PUBLIC defined.
  • simple_example.cpp: Must be compiled with MYLIB_PUBLIC defined, but not MYLIB_PRIVATE

Use target_compile_definitions(<target> <private or public> <definition(s)>) to set the definitions on simple_lib.

Solution

cmake_minimum_required(VERSION 3.14...3.18)

project(MyExample01 LANGUAGES CXX)

# This is the library Including the headers is not required, but is nice for
# users
add_library(simple_lib simple_lib.cpp simple_lib.hpp)

# The above line *did not* set the includes - we need to We can also set ., and
# it should be expanded to the current source dir

target_include_directories(simple_lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

# Adding definitions
target_compile_definitions(simple_lib PUBLIC MYLIB_PUBLIC)
target_compile_definitions(simple_lib PRIVATE MYLIB_PRIVATE)

# Now add the executable
add_executable(simple_example simple_example.cpp)

# Adding the all-important link to simple-lib
target_link_libraries(simple_example PUBLIC simple_lib)

Things you can set on targets

See more commands here.

Other types of targets

You might be really exited by targets and are already planning out how you can describe your programs in terms of targets. That’s great! However, you’ll quickly run into two more situations where the target language is useful, but you need some extra flexibility over what we’ve covered.

First, you might have a library that conceptually should be a target, but doesn’t actually have any built components - a “header-only” library. These are called interface libraries in CMake and you would write:

add_library(some_header_only_lib INTERFACE)

Notice you didn’t need to add any source files. Now you can set INTERFACE properties on this only (since there is no built component).

The second situation is if you have a pre-built library that you want to use. This is called an imported library in CMake, and uses the keyword IMPORTED. Imported libraries can also be INTERFACE libraries, they can built and modified using the same syntax as other libraries (starting in CMake 3.11), and they can have :: in their name. (ALIAS libraries, which simply rename some other library, are also allowed to have ::). Most of the time you will get imported libraries from other places, and will not be making your own.

More reading

Key Points

  • Work with targets in CMake