Working with Targets

Overview

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

Objectives
  • Know how to set up targets

  • Understand linking and INTERFACE properties

  • Make INTERFACE 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 myexample. 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 SOURCES 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_directories(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 cxx_std_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.

Things you can set on targets

See more commands here.

Other types of targets

You might be really excited 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 be 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.

INTERFACE IMPORTED

What about INTERFACE IMPORTED? The difference comes down to two things:

  1. IMPORTED targets are not exportable. If you save your targets, you can’t save IMPORTED ones - they need to be recreated (or found again).
  2. IMPORTED header include directories will always be marked as SYSTEM.

Therefore, an IMPORTED target should represent something that is not directly part of your package.

More reading

Key Points

  • Libraries and executables are targets.

  • Targets have lots of useful properties.

  • Targets can be linked to other target.

  • You can control what parts of a target get inherited when linking.

  • You can make INTERFACE targets instead of making variables.