Adding Generator Expressions.rst 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. Step 4: Adding Generator Expressions
  2. =====================================
  3. :manual:`Generator expressions <cmake-generator-expressions(7)>` are evaluated
  4. during build system generation to produce information specific to each build
  5. configuration.
  6. :manual:`Generator expressions <cmake-generator-expressions(7)>` are allowed in
  7. the context of many target properties, such as :prop_tgt:`LINK_LIBRARIES`,
  8. :prop_tgt:`INCLUDE_DIRECTORIES`, :prop_tgt:`COMPILE_DEFINITIONS` and others.
  9. They may also be used when using commands to populate those properties, such as
  10. :command:`target_link_libraries`, :command:`target_include_directories`,
  11. :command:`target_compile_definitions` and others.
  12. :manual:`Generator expressions <cmake-generator-expressions(7)>` may be used
  13. to enable conditional linking, conditional definitions used when compiling,
  14. conditional include directories and more. The conditions may be based on the
  15. build configuration, target properties, platform information or any other
  16. queryable information.
  17. There are different types of
  18. :manual:`generator expressions <cmake-generator-expressions(7)>` including
  19. Logical, Informational, and Output expressions.
  20. Logical expressions are used to create conditional output. The basic
  21. expressions are the ``0`` and ``1`` expressions. A ``$<0:...>`` results in the
  22. empty string, and ``<1:...>`` results in the content of ``...``. They can also
  23. be nested.
  24. Exercise 1 - Setting the C++ Standard with Interface Libraries
  25. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  26. Before we use :manual:`generator expressions <cmake-generator-expressions(7)>`
  27. let's refactor our existing code to use an ``INTERFACE`` library. We will
  28. use that library in the next step to demonstrate a common use for
  29. :manual:`generator expressions <cmake-generator-expressions(7)>`.
  30. Goal
  31. ----
  32. Add an ``INTERFACE`` library target to specify the required C++ standard.
  33. Helpful Resources
  34. -----------------
  35. * :command:`add_library`
  36. * :command:`target_compile_features`
  37. * :command:`target_link_libraries`
  38. Files to Edit
  39. -------------
  40. * ``CMakeLists.txt``
  41. * ``MathFunctions/CMakeLists.txt``
  42. Getting Started
  43. ---------------
  44. In this exercise, we will refactor our code to use an ``INTERFACE`` library to
  45. specify the C++ standard.
  46. The starting source code is provided in the ``Step4`` directory. In this
  47. exercise, complete ``TODO 1`` through ``TODO 3``.
  48. Start by editing the top level ``CMakeLists.txt`` file. Construct an
  49. ``INTERFACE`` library target called ``tutorial_compiler_flags`` and
  50. specify ``cxx_std_11`` as a target compiler feature.
  51. Modify ``CMakeLists.txt`` and ``MathFunctions/CMakeLists.txt`` so that all
  52. targets have a :command:`target_link_libraries` call to
  53. ``tutorial_compiler_flags``.
  54. Build and Run
  55. -------------
  56. Make a new directory called ``Step4_build``, run the :manual:`cmake <cmake(1)>`
  57. executable or the :manual:`cmake-gui <cmake-gui(1)>` to configure the project
  58. and then build it with your chosen build tool or by using ``cmake --build .``
  59. from the build directory.
  60. Here's a refresher of what that looks like from the command line:
  61. .. code-block:: console
  62. mkdir Step4_build
  63. cd Step4_build
  64. cmake ../Step4
  65. cmake --build .
  66. Next, use the newly built ``Tutorial`` and verify that it is working as
  67. expected.
  68. Solution
  69. --------
  70. Let's update our code from the previous step to use interface libraries
  71. to set our C++ requirements.
  72. To start, we need to remove the two :command:`set` calls on the variables
  73. :variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`.
  74. The specific lines to remove are as follows:
  75. .. literalinclude:: Step4/CMakeLists.txt
  76. :caption: CMakeLists.txt
  77. :name: CMakeLists.txt-CXX_STANDARD-variable-remove
  78. :language: cmake
  79. :start-after: # specify the C++ standard
  80. :end-before: # TODO 5: Create helper variables
  81. Next, we need to create an interface library, ``tutorial_compiler_flags``. And
  82. then use :command:`target_compile_features` to add the compiler feature
  83. ``cxx_std_11``.
  84. .. raw:: html
  85. <details><summary>TODO 1: Click to show/hide answer</summary>
  86. .. literalinclude:: Step5/CMakeLists.txt
  87. :caption: TODO 1: CMakeLists.txt
  88. :name: CMakeLists.txt-cxx_std-feature
  89. :language: cmake
  90. :start-after: # specify the C++ standard
  91. :end-before: # add compiler warning flags just
  92. .. raw:: html
  93. </details>
  94. Finally, with our interface library set up, we need to link our
  95. executable ``Target`` and our ``MathFunctions`` library to our new
  96. ``tutorial_compiler_flags`` library. Respectively, the code will look like
  97. this:
  98. .. raw:: html
  99. <details><summary>TODO 2: Click to show/hide answer</summary>
  100. .. literalinclude:: Step5/CMakeLists.txt
  101. :caption: TODO 2: CMakeLists.txt
  102. :name: CMakeLists.txt-target_link_libraries-step4
  103. :language: cmake
  104. :start-after: add_executable(Tutorial tutorial.cxx)
  105. :end-before: # add the binary tree to the search path for include file
  106. .. raw:: html
  107. </details>
  108. and this:
  109. .. raw:: html
  110. <details><summary>TODO 3: Click to show/hide answer</summary>
  111. .. literalinclude:: Step5/MathFunctions/CMakeLists.txt
  112. :caption: TODO 3: MathFunctions/CMakeLists.txt
  113. :name: MathFunctions-CMakeLists.txt-target_link_libraries-step4
  114. :language: cmake
  115. :start-after: # link our compiler flags interface library
  116. :end-before: # TODO 1
  117. .. raw:: html
  118. </details>
  119. With this, all of our code still requires C++ 11 to build. Notice
  120. though that with this method, it gives us the ability to be specific about
  121. which targets get specific requirements. In addition, we create a single
  122. source of truth in our interface library.
  123. Exercise 2 - Adding Compiler Warning Flags with Generator Expressions
  124. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  125. A common usage of
  126. :manual:`generator expressions <cmake-generator-expressions(7)>` is to
  127. conditionally add compiler flags, such as those for language levels or
  128. warnings. A nice pattern is to associate this information to an ``INTERFACE``
  129. target allowing this information to propagate.
  130. Goal
  131. ----
  132. Add compiler warning flags when building but not for installed versions.
  133. Helpful Resources
  134. -----------------
  135. * :manual:`cmake-generator-expressions(7)`
  136. * :command:`cmake_minimum_required`
  137. * :command:`set`
  138. * :command:`target_compile_options`
  139. Files to Edit
  140. -------------
  141. * ``CMakeLists.txt``
  142. Getting Started
  143. ---------------
  144. Start with the resulting files from Exercise 1. Complete ``TODO 4`` through
  145. ``TODO 7``.
  146. First, in the top level ``CMakeLists.txt`` file, we need to set the
  147. :command:`cmake_minimum_required` to ``3.15``. In this exercise we are going
  148. to use a generator expression which was introduced in CMake 3.15.
  149. Next we add the desired compiler warning flags that we want for our project.
  150. As warning flags vary based on the compiler, we use the
  151. ``COMPILE_LANG_AND_ID`` generator expression to control which flags to apply
  152. given a language and a set of compiler ids.
  153. Build and Run
  154. -------------
  155. Since we have our build directory already configured from Exercise 1, simply
  156. rebuild our code by calling the following:
  157. .. code-block:: console
  158. cd Step4_build
  159. cmake --build .
  160. Solution
  161. --------
  162. Update the :command:`cmake_minimum_required` to require at least CMake
  163. version ``3.15``:
  164. .. raw:: html
  165. <details><summary>TODO 4: Click to show/hide answer</summary>
  166. .. literalinclude:: Step5/CMakeLists.txt
  167. :caption: TODO 4: CMakeLists.txt
  168. :name: MathFunctions-CMakeLists.txt-minimum-required-step4
  169. :language: cmake
  170. :end-before: # set the project name and version
  171. .. raw:: html
  172. </details>
  173. Next we determine which compiler our system is currently using to build
  174. since warning flags vary based on the compiler we use. This is done with
  175. the ``COMPILE_LANG_AND_ID`` generator expression. We set the result in the
  176. variables ``gcc_like_cxx`` and ``msvc_cxx`` as follows:
  177. .. raw:: html
  178. <details><summary>TODO 5: Click to show/hide answer</summary>
  179. .. literalinclude:: Step5/CMakeLists.txt
  180. :caption: TODO 5: CMakeLists.txt
  181. :name: CMakeLists.txt-compile_lang_and_id
  182. :language: cmake
  183. :start-after: # the BUILD_INTERFACE genex
  184. :end-before: target_compile_options(tutorial_compiler_flags INTERFACE
  185. .. raw:: html
  186. </details>
  187. Next we add the desired compiler warning flags that we want for our project.
  188. Using our variables ``gcc_like_cxx`` and ``msvc_cxx``, we can use another
  189. generator expression to apply the respective flags only when the variables are
  190. true. We use :command:`target_compile_options` to apply these flags to our
  191. interface library.
  192. .. raw:: html
  193. <details><summary>TODO 6: Click to show/hide answer</summary>
  194. .. code-block:: cmake
  195. :caption: TODO 6: CMakeLists.txt
  196. :name: CMakeLists.txt-compile_flags
  197. target_compile_options(tutorial_compiler_flags INTERFACE
  198. "$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
  199. "$<${msvc_cxx}:-W3>"
  200. )
  201. .. raw:: html
  202. </details>
  203. Lastly, we only want these warning flags to be used during builds. Consumers
  204. of our installed project should not inherit our warning flags. To specify
  205. this, we wrap our flags in a generator expression using the ``BUILD_INTERFACE``
  206. condition. The resulting full code looks like the following:
  207. .. raw:: html
  208. <details><summary>TODO 7: Click to show/hide answer</summary>
  209. .. literalinclude:: Step5/CMakeLists.txt
  210. :caption: TODO 7: CMakeLists.txt
  211. :name: CMakeLists.txt-target_compile_options-genex
  212. :language: cmake
  213. :start-after: set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
  214. :end-before: # should we use our own math functions
  215. .. raw:: html
  216. </details>