Step 2: Adding a Library¶
At this point, we have seen how to create a basic project using CMake. In this step, we will learn how to create and use a library in our project. We will also see how to make the use of our library optional.
Exercise 1 - Creating a Library¶
To add a library in CMake, use the add_library()
command and specify
which source files should make up the library.
Rather than placing all of the source files in one directory, we can organize
our project with one or more subdirectories. In this case, we will create a
subdirectory specifically for our library. Here, we can add a new
CMakeLists.txt
file and one or more source files. In the top level
CMakeLists.txt
file, we will use the add_subdirectory()
command
to add the subdirectory to the build.
Once the library is created, it is connected to our executable target with
target_include_directories()
and target_link_libraries()
.
Goal¶
Add and use a library.
Helpful Resources¶
Files to Edit¶
CMakeLists.txt
tutorial.cxx
MathFunctions/CMakeLists.txt
Getting Started¶
In this exercise, we will add a library to our project that contains our own implementation for computing the square root of a number. The executable can then use this library instead of the standard square root function provided by the compiler.
For this tutorial we will put the library into a subdirectory called
MathFunctions
. This directory already contains a header file,
MathFunctions.h
, and a source file mysqrt.cxx
. We will not need to
modify either of these files. The source file has one function called
mysqrt
that provides similar functionality to the compiler's sqrt
function.
From the Help/guide/tutorial/Step2
directory, start with TODO 1
and
complete through TODO 6
.
First, fill in the one line CMakeLists.txt
in the MathFunctions
subdirectory.
Next, edit the top level CMakeLists.txt
.
Finally, use the newly created MathFunctions
library in tutorial.cxx
Build and Run¶
Run the cmake
executable or the
cmake-gui
to configure the project and then build it
with your chosen build tool.
Below is a refresher of what that looks like from the command line:
mkdir Step2_build
cd Step2_build
cmake ../Step2
cmake --build .
Try to use the newly built Tutorial
and ensure that it is still
producing accurate square root values.
Solution¶
In the CMakeLists.txt
file in the MathFunctions
directory, we create
a library target called MathFunctions
with add_library()
. The
source file for the library is passed as an argument to
add_library()
. This looks like the following line:
TODO 1: Click to show/hide answer
add_library(MathFunctions mysqrt.cxx)
To make use of the new library we will add an add_subdirectory()
call in the top-level CMakeLists.txt
file so that the library will get
built.
TODO 2: Click to show/hide answer
add_subdirectory(MathFunctions)
Next, the new library target is linked to the executable target using
target_link_libraries()
.
TODO 3: Click to show/hide answer
target_link_libraries(Tutorial PUBLIC MathFunctions)
Finally we need to specify the library's header file location. Modify
target_include_directories()
to add the MathFunctions
subdirectory
as an include directory so that the MathFunctions.h
header file can be
found.
TODO 4: Click to show/hide answer
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
Now let's use our library. In tutorial.cxx
, include MathFunctions.h
:
TODO 5: Click to show/hide answer
#include "MathFunctions.h"
Lastly, replace sqrt
with our library function mysqrt
.
TODO 6: Click to show/hide answer
const double outputValue = mysqrt(inputValue);
Exercise 2 - Making Our Library Optional¶
Now let us make the MathFunctions library optional. While for the tutorial there really isn't any need to do so, for larger projects this is a common occurrence.
CMake can do this using the option()
command. This gives users a
variable which they can change when configuring their cmake build. This
setting will be stored in the cache so that the user does not need to set
the value each time they run CMake on a build directory.
Goal¶
Add the option to build without MathFunctions
.
Helpful Resources¶
Files to Edit¶
CMakeLists.txt
tutorial.cxx
TutorialConfig.h.in
Getting Started¶
Start with the resulting files from Exercise 1. Complete TODO 7
through
TODO 13
.
First create a variable USE_MYMATH
using the option()
command
in the top-level CMakeLists.txt
file. In that same file, use that option
to determine whether to build and use the MathFunctions
library.
Then, update tutorial.cxx
and TutorialConfig.h.in
to use
USE_MYMATH
.
Build and Run¶
Since we have our build directory already configured from Exercise 1, we can rebuild by simply calling the following:
cd ../Step2_build
cmake --build .
Next, run the Tutorial
executable on a few numbers to verify that it's
still correct.
Now let's update the value of USE_MYMATH
to OFF
. The easiest way is to
use the cmake-gui
or ccmake
if you're in the terminal. Or, alternatively, if you want to change the
option from the command-line, try:
cmake ../Step2 -DUSE_MYMATH=OFF
Now, rebuild the code with the following:
cmake --build .
Then, run the executable again to ensure that it still works with
USE_MYMATH
set to OFF
. Which function gives better results, sqrt
or mysqrt
?
Solution¶
The first step is to add an option to the top-level CMakeLists.txt
file.
This option will be displayed in the cmake-gui
and
ccmake
with a default value of ON
that can be
changed by the user.
TODO 7: Click to show/hide answer
option(USE_MYMATH "Use tutorial provided math implementation" ON)
Next, make building and linking the MathFunctions
library
conditional.
Start by creating a list()
of the optional library targets for our
project. At the moment, it is just MathFunctions
. Let's name our list
EXTRA_LIBS
.
Similarly, we need to make a list()
for the optional includes which
we will call EXTRA_INCLUDES
. In this list, we will APPEND
the path of
the header file needed for our library.
Next, create an if()
statement which checks the value of
USE_MYMATH
. Inside the if()
block, put the
add_subdirectory()
command from Exercise 1 with the additional
list()
commands.
When USE_MYMATH
is ON
, the lists will be generated and will be added to
our project. When USE_MYMATH
is OFF
, the lists stay empty. With this
strategy, we allow users to toggle USE_MYMATH
to manipulate what library is
used in the build.
The top-level CMakeLists.txt file will now look like the following:
TODO 8: Click to show/hide answer
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
Now that we have these two lists, we need to update
target_link_libraries()
and target_include_directories()
to
use them. Changing them is fairly straightforward.
For target_link_libraries()
, we replace the written out
library names with EXTRA_LIBS
. This looks like the following:
TODO 9: Click to show/hide answer
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
Then, we do the same thing with target_include_directories()
and
EXTRA_INCLUDES
.
TODO 10: Click to show/hide answer
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
Note that this is a classic approach when dealing with many components. We will cover the modern approach in the Step 3 of the tutorial.
The corresponding changes to the source code are fairly straightforward.
First, in tutorial.cxx
, we include the MathFunctions.h
header if
USE_MYMATH
is defined.
TODO 11: Click to show/hide answer
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
Then, in the same file, we make USE_MYMATH
control which square root
function is used:
TODO 12: Click to show/hide answer
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
Since the source code now requires USE_MYMATH
we can add it to
TutorialConfig.h.in
with the following line:
TODO 13: Click to show/hide answer
#cmakedefine USE_MYMATH
With these changes, our library is now completely optional to whoever is building and using it.
Bonus Question¶
Why is it important that we configure TutorialConfig.h.in
after the option for USE_MYMATH
? What would happen if we inverted the two?
Answer¶
Click to show/hide answer
We configure after because TutorialConfig.h.in
uses the value of
USE_MYMATH
. If we configure the file before
calling option()
, we won't be using the expected value of
USE_MYMATH
.