| Step 4: In-Depth CMake Target Commands |
| ====================================== |
| |
| There are several target commands within CMake we can use to describe |
| requirements. As a reminder, a target command is one which modifies the |
| properties of the target it is applied to. These properties describe |
| requirements needed to build the software, such as sources, compile flags, |
| and output names; or properties necessary to consume the target, such as header |
| includes, library directories, and linkage rules. |
| |
| .. note:: |
| As discussed in ``Step1``, properties required to build a target should be |
| described with the ``PRIVATE`` :ref:`scope keyword <Target Command Scope>`, |
| those required to consume the target with ``INTERFACE``, and properties needed |
| for both are described with ``PUBLIC``. |
| |
| In this step we will go over all the available target commands in CMake. Not all |
| target commands are created equal. We have already discussed the two most |
| important target commands, :command:`target_sources` and |
| :command:`target_link_libraries`. Of the remaining commands, some are almost |
| as common as these two, others have more advanced applications, and a couple |
| should only be used as a last resort when other options are not available. |
| |
| Background |
| ^^^^^^^^^^ |
| |
| Before going any further, let's name all of the CMake target commands. We'll |
| split these into three groups: the recommended and generally useful commands, |
| the advanced and cautionary commands, and the "footgun" commands which should |
| be avoided unless necessary. |
| |
| +-----------------------------------------+--------------------------------------+---------------------------------------+ |
| | Common/Recommended | Advanced/Caution | Esoteric/Footguns | |
| +=========================================+======================================+=======================================+ |
| | :command:`target_compile_definitions` | :command:`get_target_property` | :command:`target_include_directories` | |
| | :command:`target_compile_features` | :command:`set_target_properties` | :command:`target_link_directories` | |
| | :command:`target_link_libraries` | :command:`target_compile_options` | | |
| | :command:`target_sources` | :command:`target_link_options` | | |
| | | :command:`target_precompile_headers` | | |
| +-----------------------------------------+--------------------------------------+---------------------------------------+ |
| |
| .. note:: |
| There's no such thing as a "bad" CMake target command. They all have valid |
| use cases. This categorization is provided to give newcomers a simple |
| intuition about which commands they should consider first when tackling |
| a problem. |
| |
| We'll demonstrate most of these in the following exercises. The three we won't |
| be using are :command:`get_target_property`, :command:`set_target_properties` |
| and :command:`target_precompile_headers`, so we will briefly discuss their |
| purpose here. |
| |
| The :command:`get_target_property` and :command:`set_target_properties` commands |
| give direct access to a target's properties by name. They can even be used |
| to attach arbitrary property names to a target. |
| |
| .. code-block:: cmake |
| |
| add_library(Example) |
| set_target_properties(Example |
| PROPERTIES |
| Key Value |
| Hello World |
| ) |
| |
| get_target_property(KeyVar Example Key) |
| get_target_property(HelloVar Example Hello) |
| |
| message("Key: ${KeyVar}") |
| message("Hello: ${HelloVar}") |
| |
| .. code-block:: console |
| |
| $ cmake -B build |
| ... |
| Key: Value |
| Hello: World |
| |
| The full list of target properties which are semantically meaningful to CMake |
| are documented at :manual:`cmake-properties(7)`, however most of these should |
| be modified with their dedicated commands. For example, it is unnecessary to |
| directly manipulate ``LINK_LIBRARIES`` and ``INTERFACE_LINK_LIBRARIES``, as |
| these are handled by :command:`target_link_libraries`. |
| |
| Conversely, some lesser-used properties are only accessible via these commands. |
| The :prop_tgt:`DEPRECATION` property, used to attach deprecation notices to |
| targets, can only be set via :command:`set_target_properties`; as can the |
| :prop_tgt:`ADDITIONAL_CLEAN_FILES`, for describing additional files to be |
| removed by CMake's ``clean`` target; and other properties of this sort. |
| |
| The :command:`target_precompile_headers` command takes a list of header files, |
| similar to :command:`target_sources`, and creates a precompiled header from |
| them. This precompiled header is then force included into all translation |
| units in the target. This can be useful for build performance. |
| |
| Exercise 1 - Features and Definitions |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| In earlier steps we cautioned against globally setting |
| :variable:`CMAKE_<LANG>_STANDARD` and overriding packagers' decision concerning |
| which language standard to use. On the other hand, many libraries have a |
| minimum required feature set they need in order to build, and for these it |
| is appropriate to use the :command:`target_compile_features` command to |
| communicate those requirements. |
| |
| .. code-block:: cmake |
| |
| target_compile_features(MyApp PRIVATE cxx_std_20) |
| |
| The :command:`target_compile_features` command describes a minimum language |
| standard as a target property. If the :variable:`CMAKE_<LANG>_STANDARD` is above |
| this version, or the compiler default already provides this language standard, |
| no action is taken. If additional flags are necessary to enable the standard, |
| these will be added by CMake. |
| |
| .. note:: |
| :command:`target_compile_features` manipulates the same style of interface and |
| non-interface properties as the other target commands. This means it is |
| possible to *inherit* a language standard requirement specified with |
| ``INTERFACE`` or ``PUBLIC`` scope keywords. |
| |
| If language features are used only in implementation files, then the |
| respective compile features should be ``PRIVATE``. If the target's headers |
| use the features, then ``PUBLIC`` or ``INTERFACE`` should be used. |
| |
| For C++, the compile features are of the form ``cxx_std_YY`` where ``YY`` is |
| the standardization year, e.g. ``14``, ``17``, ``20``, etc. |
| |
| The :command:`target_compile_definitions` command describes compile definitions |
| as target properties. It is the most common mechanism for communicating build |
| configuration information to the source code itself. As with all properties, |
| the scope keywords apply as we have discussed. |
| |
| .. code-block:: cmake |
| |
| target_compile_definitions(MyLibrary |
| PRIVATE |
| MYLIBRARY_USE_EXPERIMENTAL_IMPLEMENTATION |
| |
| PUBLIC |
| MYLIBRARY_EXCLUDE_DEPRECATED_FUNCTIONS |
| ) |
| |
| It is neither required nor desired that we attach ``-D`` prefixes to compile |
| definitions described with :command:`target_compile_definitions`. CMake will |
| determine the correct flag for the current compiler. |
| |
| Goal |
| ---- |
| |
| Use :command:`target_compile_features` and :command:`target_compile_definitions` |
| to communicate language standard and compile definition requirements. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :command:`target_compile_features` |
| * :command:`target_compile_definitions` |
| * :command:`option` |
| * :command:`if` |
| |
| Files to Edit |
| ------------- |
| |
| * ``Tutorial/CMakeLists.txt`` |
| * ``MathFunctions/CMakeLists.txt`` |
| * ``MathFunctions/MathFunctions.cxx`` |
| * ``CMakePresets.json`` |
| |
| Getting Started |
| --------------- |
| |
| The ``Help/guide/tutorial/Step4`` directory contains the complete, recommended |
| solution to ``Step3`` and relevant ``TODOs`` for this step. Complete ``TODO 1`` |
| through ``TODO 8``. |
| |
| Build and Run |
| ------------- |
| |
| We can run CMake using our ``tutorial`` preset, and then build as usual. |
| |
| .. code-block:: console |
| |
| cmake --preset tutorial |
| cmake --build build |
| |
| Verify that the output of ``Tutorial`` is what we would expect for ``std::sqrt``. |
| |
| Solution |
| -------- |
| |
| First we add a new option to the top-level CML. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 1: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step5/CMakeLists.txt |
| :caption: TODO 1: CMakeLists.txt |
| :name: CMakeLists.txt-TUTORIAL_USE_STD_SQRT |
| :language: cmake |
| :start-at: option(TUTORIAL_BUILD_UTILITIES |
| :end-at: option(TUTORIAL_USE_STD_SQRT |
| |
| .. raw:: html |
| |
| </details> |
| |
| Then we add the compile feature and definitions to ``MathFunctions``. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 2-3: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step5/MathFunctions/CMakeLists.txt |
| :caption: TODO 2-3: MathFunctions/CMakeLists.txt |
| :name: MathFunctions/CMakeLists.txt-target_compile_features |
| :language: cmake |
| :start-at: target_compile_features |
| :end-at: endif() |
| |
| .. raw:: html |
| |
| </details> |
| |
| And the compile feature for ``Tutorial``. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 4: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step5/Tutorial/CMakeLists.txt |
| :caption: TODO 4: Tutorial/CMakeLists.txt |
| :name: Tutorial/CMakeLists.txt-target_compile_features |
| :language: cmake |
| :start-at: target_compile_features |
| :end-at: target_compile_features |
| |
| .. raw:: html |
| |
| </details> |
| |
| Now we can modify ``MathFunctions`` to take advantage of the new definition. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 5-6: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step5/MathFunctions/MathFunctions.cxx |
| :caption: TODO 5: MathFunctions/MathFunctions.cxx |
| :name: MathFunctions/MathFunctions.cxx-cmath |
| :language: c++ |
| :start-at: cmath |
| :end-at: format |
| :append: #include <iostream> |
| |
| .. literalinclude:: Step5/MathFunctions/MathFunctions.cxx |
| :caption: TODO 6: MathFunctions/MathFunctions.cxx |
| :name: MathFunctions/MathFunctions.cxx-std-sqrt |
| :language: c++ |
| :start-at: double sqrt(double x) |
| :end-at: } |
| |
| .. raw:: html |
| |
| </details> |
| |
| Finally we can update our ``CMakePresets.json``. We don't need to set |
| ``CMAKE_CXX_STANDARD`` anymore, but we do want to try out our new |
| compile definition. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 7-8: Click to show/hide answer</summary> |
| |
| .. code-block:: json |
| :caption: TODO 7-8: CMakePresets.json |
| :name: CMakePresets.json-std-sqrt |
| |
| "cacheVariables": { |
| "TUTORIAL_USE_STD_SQRT": "ON" |
| } |
| |
| .. raw:: html |
| |
| </details> |
| |
| Exercise 2 - Compile and Link Options |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| Sometimes, we need to exercise specific control over the exact options being |
| passed on the compile and link line. These situations are addressed by |
| :command:`target_compile_options` and :command:`target_link_options`. |
| |
| .. code:: cmake |
| |
| target_compile_options(MyApp PRIVATE -Wall -Werror) |
| target_link_options(MyApp PRIVATE -T LinksScript.ld) |
| |
| There are several problems with unconditionally calling |
| :command:`target_compile_options` or :command:`target_link_options`. The primary |
| problem is compiler flags are specific to the compiler frontend being used. In |
| order to ensure that our project supports multiple compiler frontends, we must |
| only pass compatible flags to the compiler. |
| |
| We can achieve this by checking the :variable:`CMAKE_<LANG>_COMPILER_FRONTEND_VARIANT` |
| variable which tells us the style of flags supported by the compiler frontend. |
| |
| .. note:: |
| Prior to CMake 3.26, :variable:`CMAKE_<LANG>_COMPILER_FRONTEND_VARIANT` was |
| only set for compilers with multiple frontend variants. In versions after |
| CMake 3.26 checking this variable alone is sufficient. |
| |
| However this tutorial targets CMake 3.23. As such, the logic is more |
| complicated than we have time for here. This tutorial step already includes |
| correct logic for checking the compiler variant for MSVC, GCC, Clang, and |
| AppleClang on CMake 3.23. |
| |
| Even if a compiler accepts the flags we pass, the semantics of compiler flags |
| change over time. This is especially true with regards to warnings. Projects |
| should not turn warnings-as-error flags by default, as this can break their |
| build on otherwise innocuous compiler warnings included in later releases. |
| |
| .. note:: |
| For errors and warnings, consider placing flags in :variable:`CMAKE_<LANG>_FLAGS` |
| for local development builds and during CI runs (via preset or |
| :option:`-D <cmake -D>` flags). We know exactly which compiler and |
| toolchain are being used in these contexts, so we can customize the behavior |
| precisely without risking build breakages on other platforms. |
| |
| Goal |
| ---- |
| |
| Add appropriate warning flags to the ``Tutorial`` executable for MSVC-style and |
| GNU-style compiler frontends. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :command:`target_compile_options` |
| |
| Files to Edit |
| ------------- |
| |
| * ``Tutorial/CMakeLists.txt`` |
| |
| Getting Started |
| --------------- |
| |
| Continue editing files in the ``Step4`` directory. The conditional for checking |
| the frontend variant has already been written. Complete ``TODO 9`` and |
| ``TODO 10`` to add warning flags to ``Tutorial``. |
| |
| Build and Run |
| ------------- |
| |
| Since we have already configured for this step, we can build with the usual |
| command. |
| |
| .. code-block:: cmake |
| |
| cmake --build build |
| |
| This should reveal a simple warning in the build. You can go ahead and fix it. |
| |
| Solution |
| -------- |
| |
| We need to add two compile options to ``Tutorial``, one MSVC-style flag and |
| one GNU-style flag. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 9-10: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step5/Tutorial/CMakeLists.txt |
| :caption: TODO 9-10: Tutorial/CMakeLists.txt |
| :name: Tutorial/CMakeLists.txt-target_compile_options |
| :language: cmake |
| :start-at: if( |
| :end-at: endif() |
| |
| .. raw:: html |
| |
| </details> |
| |
| Exercise 3 - Include and Link Directories |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| .. note:: |
| This exercise requires building an archive using a compiler directly on the |
| command line. It is not used in later steps. It is included only to |
| demonstrate a use case for :command:`target_include_directories` and |
| :command:`target_link_directories`. |
| |
| If you cannot complete this exercise for whatever reason feel free to treat |
| it as informational-only, or skip it entirely. |
| |
| It is generally unnecessary to directly describe include and link directories, |
| as these requirements are inherited when linking together targets generated |
| within CMake, or from external dependencies imported into CMake with commands |
| we will cover in later steps. |
| |
| If we happen to have some libraries or header files which are not described |
| by a CMake target which we need to bring into the build, perhaps pre-compiled |
| binaries provided by a vendor, we can incorporate with the |
| :command:`target_link_directories` and :command:`target_include_directories` |
| commands. |
| |
| .. code-block:: cmake |
| |
| target_link_directories(MyApp PRIVATE Vendor/lib) |
| target_include_directories(MyApp PRIVATE Vendor/include) |
| |
| |
| These commands use properties which map to the ``-L`` and ``-I`` compiler flags |
| (or whatever flags the compiler uses for link and include directories). |
| |
| Of course, passing a link directory doesn't tell the compiler to link anything |
| into the build. For that we need :command:`target_link_libraries`. When |
| :command:`target_link_libraries` is given an argument which does not map to |
| a target name, it will add the string directly to the link line as a library |
| to be linked into the build (prepending any appropriate flags, such a ``-l``). |
| |
| Goal |
| ---- |
| |
| Describe a pre-compiled, vendored, static library and its headers inside a |
| project using :command:`target_link_directories` and |
| :command:`target_include_directories`. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :command:`target_link_directories` |
| * :command:`target_include_directories` |
| * :command:`target_link_libraries` |
| |
| Files to Edit |
| ------------- |
| |
| * ``Vendor/CMakeLists.txt`` |
| * ``Tutorial/CMakeLists.txt`` |
| |
| Getting Started |
| --------------- |
| |
| You will need to build the vendor library into a static archive to complete this |
| exercise. Navigate to the ``Help/guide/tutorial/Step4/Vendor/lib`` directory |
| and build the code as appropriate for your platform. On Unix-like operating |
| systems the appropriate commands are usually: |
| |
| .. code-block:: console |
| |
| g++ -c Vendors.cxx |
| ar rvs libVendor.a Vendor.o |
| |
| Then complete ``TODO 11`` through ``TODO 14``. |
| |
| .. note:: |
| ``VendorLib`` is an ``INTERFACE`` library, meaning it has no build requirements |
| (because it has already been built). All of its properties should also be |
| interface properties. |
| |
| We'll discuss ``INTERFACE`` libraries in greater depth during the next step. |
| |
| |
| Build and Run |
| ------------- |
| |
| If you have successfully built ``libVendor``, you can rebuild ``Tutorial`` |
| using the normal command. |
| |
| .. code-block:: console |
| |
| cmake --build build |
| |
| Running ``Tutorial`` should now output a message about the acceptability of the |
| result to the vendor. |
| |
| Solution |
| -------- |
| |
| We need to use the target link and include commands to describe the archive |
| and its headers as ``INTERFACE`` requirements of ``VendorLib``. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 11-13: Click to show/hide answer</summary> |
| |
| .. code-block:: cmake |
| :caption: TODO 11-13: Vendor/CMakeLists.txt |
| :name: Vendor/CMakeLists.txt |
| |
| target_include_directories(VendorLib |
| INTERFACE |
| include |
| ) |
| |
| target_link_directories(VendorLib |
| INTERFACE |
| lib |
| ) |
| |
| target_link_libraries(VendorLib |
| INTERFACE |
| Vendor |
| ) |
| |
| .. raw:: html |
| |
| </details> |
| |
| Then we can add ``VendorLib`` to ``Tutorial``'s linked libraries. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 14: Click to show/hide answer</summary> |
| |
| .. code-block:: cmake |
| :caption: TODO 14: Tutorial/CMakeLists.txt |
| :name: Tutorial/CMakeLists.txt-VendorLib |
| |
| target_link_libraries(Tutorial |
| PRIVATE |
| MathFunctions |
| VendorLib |
| ) |
| |
| .. raw:: html |
| |
| </details> |