Target Properties
Learn to work with target properties - understanding their purpose, setting and updating them, and retrieving them when your build requires it
In our journey so far, we've gone from the fundamentals of the C++ build process to writing our first complete, multi-directory CMake project. We've learned the core CMake language constructs: variables, lists, control flow, and functions.
We've been using commands like add_library()
and target_link_libraries()
, but we've treated them as simple instructions. The real power of modern CMake, however, comes from a shift in thinking. We should stop seeing our build script as a sequence of commands and start seeing it as a description of how targets relate to each other.
A target isn't just a name for an output file; it's a first-class object with properties and relationships. We'll explore the different types of targets, learn how they store information in properties, and, most importantly, master the PUBLIC
, PRIVATE
, and INTERFACE
keywords that control how these properties are shared between targets that are linked together.
In this lesson, we will focus on the properties associated with a target, and then move on to the relationships between targets in the next lesson.
Target Properties and Meta-Information
Every target you create is an object that holds a collection of properties. These properties are simply data that is attached to the target. They define everything about the target: its source files, its include directories, its compiler flags, its dependencies, and much more.
We're not just restricted to using CMake-recognized properties. If we need to attach some custom data to a target that we'll need to access later, we can do that.
Most of the target_*
commands we've seen are just user-friendly wrappers for setting properties that are important to CMake. For example, imagine we use the following command:
target_compile_features(MyTarget PUBLIC cxx_std_20)
Behind the scenes, this is setting the COMPILE_FEATURES
property on the MyTarget
object.
While you'll usually use the high-level target_*
commands where they're available, you can also directly manipulate properties with set_target_properties()
and get_target_property()
.
Using get_target_property()
We'll see how to set properties in the next section, but there are some properties that CMake provides and manages on our targets by default. For example, targets have a TYPE
property, which we can examine if we need to know if the target is an executable or library.
The full list of types are available on the official documentation, but we'll highlight many of the most useful ones throughout the course.
We retrieve a property on a target using the get_target_property()
command, which requires three arguments:
- The name of the variable we want the property's value saved to
- The name of the target
- The name of the property on the target
The following example retrieves the type of our target, and saves it as a variable called target_type
:
greeter/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
add_library(GreeterLib src/Greeter.cpp)
get_target_property(target_type GreeterLib TYPE)
message("GreeterLib has type ${target_type}")
GreeterLib has type STATIC_LIBRARY
Using set_target_properties()
To set properties on a target, we use the set_target_properties()
command. This function can update any number of targets in a single command, and can set any number of properties on those targets.
To do this, we provide the following sets of arguments, in order:
- The targets we want to set the property on - each target is a separate argument
- The
PROPERTIES
keyword - The properties we want to set, as a collection of key-value pairs. Each pair is two arguments - they key, and the value
The following command updates the GreeterApp
and GreeterLib
targets with the same two properties - FAVORITE_FRUIT
set to Apple
, and FAVORITE_VEGETABLE
set to Potato
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Greeter VERSION "1.0")
add_subdirectory(greeter)
add_subdirectory(app)
set_target_properties(
# Targets to update
GreeterApp GreeterLib
PROPERTIES
# Properties to set
FAVORITE_FRUIT "Apple"
FAVORITE_VEGETABLE "Potato"
)
get_target_property(app_fruit GreeterApp FAVORITE_FRUIT)
message("GreeterApp's favorite fruit is ${app_fruit}")
get_target_property(lib_fruit GreeterLib FAVORITE_FRUIT)
if(lib_fruit STREQUAL "Apple")
message("It is GreaterLib's favorite too")
endif()
GreeterApp's favorite fruit is Apple
It is GreaterLib's favorite too
CMake Target Properties
There are many target properties that CMake uses behind the scenes, but has not provided a dedicated function for setting. They're all listed on the official documentation, but we'll highlight many of the most useful ones throughout this course.
For example, we can change the name of the file that a target outputs by manually setting the OUTPUT_NAME
property.
Below, we change our executable's name from "GreeterApp" to "Greeter" (or from "GreeterApp.exe" to "Greeter.exe" on Windows):
app/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Greeter VERSION "1.0")
add_subdirectory(greeter)
add_subdirectory(app)
set_target_properties(GreeterApp PROPERTIES
OUTPUT_NAME "Greeter"
)
Building our project, we should now see the executable in /build/app
is now being named Greeter
(or Greeter.exe
on Windows):
cmake --build .
./app/Greeter
Hello from the modular Greeter library!
This level of control is useful, and we'll revisit properties several times in the rest of the course. However, the real magic of modern CMake comes from how these properties are shared between targets.
The PUBLIC
keyword we've occassionally been passing to our commands relates to this sharing process, so let's finally learn what that does in the next lesson.
Summary
This lesson introduced the philosophical core of modern CMake. By treating targets as objects and defining their properties, we create a self-describing system that manages complexity for us.
Targets are Objects: Think of add_library()
and add_executable()
as creating objects. Commands like target_include_directories()
are methods that modify the state of these objects.
Reading and Writing Properties: We use get_target_property()
to retrieve a property from a target, and set_target_properties()
to set one or more properties on one or more targets.
System-Provided Properties: Some properties on targets are automatically set by CMake, such as TYPE
. Some properties can be maintained through friendly helper functions like target_include_directories()
. Some properties that we can set will change how CMake handles the target, such as OUTPUT_NAME
.
Relationships Between Targets
Learn about target types, and how the PUBLIC
, PRIVATE
, and INTERFACE
keywords control how properties are shared.