Step 3: Adding Usage Requirements for a Library¶
Exercise 1 - Adding Usage Requirements for a Library¶
Usage requirements of a target parameters allow for far better control over a library or executable's link and include line while also giving more control over the transitive property of targets inside CMake. The primary commands that leverage usage requirements are:
Goal¶
Add usage requirements for a library.
Helpful Materials¶
Files to Edit¶
MathFunctions/CMakeLists.txt
CMakeLists.txt
Getting Started¶
In this exercise, we will refactor our code from
Adding a Library
to use the modern CMake approach. We will
let our library define its own usage requirements so they are passed
transitively to other targets as necessary. In this case, MathFunctions
will specify any needed include directories itself. Then, the consuming target
Tutorial
simply needs to link to MathFunctions
and not worry about
any additional include directories.
The starting source code is provided in the Step3
directory. In this
exercise, complete TODO 1
through TODO 3
.
First, add a call to target_include_directories()
in
MathFunctions/CMakeLists
. Remember that
CMAKE_CURRENT_SOURCE_DIR
is the path to the source directory
currently being processed.
Then, update (and simplify!) the call to
target_include_directories()
in the top-level CMakeLists.txt
.
Build and Run¶
Make a new directory called Step3_build
, run the cmake
executable or the cmake-gui
to
configure the project and then build it with your chosen build tool or by
using cmake --build .
from the build directory.
Here's a refresher of what that looks like from the command line:
mkdir Step3_build
cd Step3_build
cmake ../Step3
cmake --build .
Next, use the newly built Tutorial
and verify that it is working as
expected.
Solution¶
Let's update the code from the previous step to use the modern CMake approach of usage requirements.
We want to state that anybody linking to MathFunctions
needs to include
the current source directory, while MathFunctions
itself doesn't. This
can be expressed with an INTERFACE
usage requirement. Remember
INTERFACE
means things that consumers require but the producer doesn't.
At the end of MathFunctions/CMakeLists.txt
, use
target_include_directories()
with the INTERFACE
keyword, as
follows:
TODO 1: Click to show/hide answer
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
Now that we've specified usage requirements for MathFunctions
we can
safely remove our uses of the EXTRA_INCLUDES
variable from the top-level
CMakeLists.txt
.
Remove this line:
TODO 2: Click to show/hide answer
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
And remove EXTRA_INCLUDES
from target_include_directories
:
TODO 3: Click to show/hide answer
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
Notice that with this technique, the only thing our executable target does to
use our library is call target_link_libraries()
with the name
of the library target. In larger projects, the classic method of specifying
library dependencies manually becomes very complicated very quickly.
Exercise 2 - Setting the C++ Standard with Interface Libraries¶
Now that we have switched our code to a more modern approach, let's demonstrate a modern technique to set properties to multiple targets.
Let's refactor our existing code to use an INTERFACE
library. We will
use that library in the next step to demonstrate a common use for
generator expressions
.
Goal¶
Add an INTERFACE
library target to specify the required C++ standard.
Helpful Resources¶
Files to Edit¶
CMakeLists.txt
MathFunctions/CMakeLists.txt
Getting Started¶
In this exercise, we will refactor our code to use an INTERFACE
library to
specify the C++ standard.
Start this exercise from what we left at the end of Step3 exercise 1. You will
have to complete TODO 4
through TODO 7
.
Start by editing the top level CMakeLists.txt
file. Construct an
INTERFACE
library target called tutorial_compiler_flags
and
specify cxx_std_11
as a target compiler feature.
Modify CMakeLists.txt
and MathFunctions/CMakeLists.txt
so that all
targets have a target_link_libraries()
call to
tutorial_compiler_flags
.
Build and Run¶
Since we have our build directory already configured from Exercise 1, simply rebuild our code by calling the following:
cd Step3_build
cmake --build .
Next, use the newly built Tutorial
and verify that it is working as
expected.
Solution¶
Let's update our code from the previous step to use interface libraries to set our C++ requirements.
To start, we need to remove the two set()
calls on the variables
CMAKE_CXX_STANDARD
and CMAKE_CXX_STANDARD_REQUIRED
.
The specific lines to remove are as follows:
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
Next, we need to create an interface library, tutorial_compiler_flags
. And
then use target_compile_features()
to add the compiler feature
cxx_std_11
.
TODO 4: Click to show/hide answer
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
Finally, with our interface library set up, we need to link our
executable Tutorial
, our SqrtLibrary
library and our MathFunctions
library to our new tutorial_compiler_flags
library. Respectively, the code
will look like this:
TODO 5: Click to show/hide answer
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
this:
TODO 6: Click to show/hide answer
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
and this:
TODO 7: Click to show/hide answer
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
With this, all of our code still requires C++ 11 to build. Notice though that with this method, it gives us the ability to be specific about which targets get specific requirements. In addition, we create a single source of truth in our interface library.