Step 3: Adding Usage Requirements for a Library =============================================== Exercise 1 - Adding Usage Requirements for a Library ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :ref:`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: * :command:`target_compile_definitions` * :command:`target_compile_options` * :command:`target_include_directories` * :command:`target_link_directories` * :command:`target_link_options` * :command:`target_precompile_headers` * :command:`target_sources` Goal ---- Add usage requirements for a library. Helpful Materials ----------------- * :variable:`CMAKE_CURRENT_SOURCE_DIR` Files to Edit ------------- * ``MathFunctions/CMakeLists.txt`` * ``CMakeLists.txt`` Getting Started --------------- In this exercise, we will refactor our code from :guide:`tutorial/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 :command:`target_include_directories` in ``MathFunctions/CMakeLists``. Remember that :variable:`CMAKE_CURRENT_SOURCE_DIR` is the path to the source directory currently being processed. Then, update (and simplify!) the call to :command:`target_include_directories` in the top-level ``CMakeLists.txt``. Build and Run ------------- Make a new directory called ``Step3_build``, run the :manual:`cmake ` executable or the :manual:`cmake-gui ` to configure the project and then build it with your chosen build tool or by using :option:`cmake --build . ` from the build directory. Here's a refresher of what that looks like from the command line: .. code-block:: console 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 :command:`target_include_directories` with the ``INTERFACE`` keyword, as follows: .. raw:: html
TODO 1: Click to show/hide answer .. literalinclude:: Step4/MathFunctions/CMakeLists.txt :caption: TODO 1: MathFunctions/CMakeLists.txt :name: MathFunctions/CMakeLists.txt-target_include_directories-INTERFACE :language: cmake :start-after: # to find MathFunctions.h :end-before: # should we use our own .. raw:: html
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: .. raw:: html
TODO 2: Click to show/hide answer .. literalinclude:: Step3/CMakeLists.txt :caption: TODO 2: CMakeLists.txt :name: CMakeLists.txt-remove-EXTRA_INCLUDES :language: cmake :start-after: add_subdirectory(MathFunctions) :end-before: # add the executable .. raw:: html
And the lines: .. raw:: html
TODO 3: Click to show/hide answer .. literalinclude:: Step4/CMakeLists.txt :caption: TODO 3: CMakeLists.txt :name: CMakeLists.txt-target_include_directories-remove-EXTRA_INCLUDES :language: cmake :start-after: # so that we will find TutorialConfig.h .. raw:: html
The remaining code looks like: .. raw:: html
Click to show/hide the resulting code .. literalinclude:: Step4/CMakeLists.txt :caption: Remaining code after removing EXTRA_INCLUDES :name: CMakeLists.txt-after-removing-EXTRA_INCLUDES :language: cmake :start-after: add_subdirectory(MathFunctions) .. raw:: html
Notice that with this technique, the only thing our executable target does to use our library is call :command:`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 :manual:`generator expressions `. Goal ---- Add an ``INTERFACE`` library target to specify the required C++ standard. Helpful Resources ----------------- * :command:`add_library` * :command:`target_compile_features` * :command:`target_link_libraries` 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 :command:`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: .. code-block:: console 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 :command:`set` calls on the variables :variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`. The specific lines to remove are as follows: .. literalinclude:: Step3/CMakeLists.txt :caption: CMakeLists.txt :name: CMakeLists.txt-CXX_STANDARD-variable-remove :language: cmake :start-after: # specify the C++ standard :end-before: # configure a header file Next, we need to create an interface library, ``tutorial_compiler_flags``. And then use :command:`target_compile_features` to add the compiler feature ``cxx_std_11``. .. raw:: html
TODO 4: Click to show/hide answer .. literalinclude:: Step4/CMakeLists.txt :caption: TODO 4: CMakeLists.txt :name: CMakeLists.txt-cxx_std-feature :language: cmake :start-after: # specify the C++ standard :end-before: # TODO 2: Create helper .. raw:: html
Finally, with our interface library set up, we need to link our executable ``Target``, our ``MathFunctions`` library, and our ``SqrtLibrary`` library to our new ``tutorial_compiler_flags`` library. Respectively, the code will look like this: .. raw:: html
TODO 5: Click to show/hide answer .. literalinclude:: Step4/CMakeLists.txt :caption: TODO 5: CMakeLists.txt :name: CMakeLists.txt-target_link_libraries-step4 :language: cmake :start-after: add_executable(Tutorial tutorial.cxx) :end-before: # add the binary tree to the search path for include file .. raw:: html
this: .. raw:: html
TODO 6: Click to show/hide answer .. literalinclude:: Step4/MathFunctions/CMakeLists.txt :caption: TODO 6: MathFunctions/CMakeLists.txt :name: MathFunctions-CMakeLists.txt-target_link_libraries-step4 :language: cmake :start-after: # link our compiler flags interface library :end-before: target_link_libraries(MathFunctions .. raw:: html
and this: .. raw:: html
TODO 7: Click to show/hide answer .. literalinclude:: Step4/MathFunctions/CMakeLists.txt :caption: TODO 7: MathFunctions/CMakeLists.txt :name: MathFunctions-SqrtLibrary-target_link_libraries-step4 :language: cmake :start-after: target_link_libraries(SqrtLibrary :end-before: endif() .. raw:: html
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.