CMake Language Fundamentals.rst 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. Step 2: CMake Language Fundamentals
  2. ===================================
  3. In the previous step we rushed through and handwaved several aspects of the
  4. CMake language which is used within ``CMakeLists.txt`` in order to get useful,
  5. building programs as soon as possible. However, in the wild we encounter
  6. a great deal more complexity than simply describing lists of source and
  7. header files.
  8. To deal with this complexity CMake provides a Turing-complete domain-specific
  9. language for describing the process of building software. Understanding the
  10. fundamentals of this language will be necessary as we write more complex
  11. CMLs and other CMake files. The language is formally known as
  12. ":manual:`CMake Language <cmake-language(7)>`", or more colloquially as CMakeLang.
  13. .. note::
  14. The CMake Language is not well suited to describing things which are not
  15. related to building software. While it has some features for general purpose
  16. use, developers should use caution when solving problems not directly related
  17. to their build in CMake Language.
  18. Oftentimes the correct answer is to write a tool in a general purpose
  19. programming language which solves the problem, and teach CMake how to invoke
  20. that tool as part of the build process. Code generation, cryptographic
  21. signature utilities, and even ray-tracers have been written in CMake Language,
  22. but this is not a recommended practice.
  23. Because we want to fully explore the language features, this step is an
  24. exception to the tutorial sequencing. It neither builds on ``Step1``, nor is the
  25. starting point for ``Step3``. This will be a sandbox to explore language
  26. features without building any software. We'll pick back up with the Tutorial
  27. program in ``Step3``.
  28. .. note::
  29. This tutorial endeavors to demonstrate best practices and solutions to real
  30. problems. However, for this one step we're going to be re-implementing some
  31. built-in CMake functions. In "real life", do not write your own
  32. :command:`list(APPEND)`.
  33. Background
  34. ^^^^^^^^^^
  35. The only fundamental types in CMakeLang are strings and lists. Every object in
  36. CMake is a string, and lists are themselves strings which contain semicolons
  37. as separators. Any command which appears to operate on something other than a
  38. string, whether they be booleans, numbers, JSON objects, or otherwise, is in
  39. fact consuming a string, doing some internal conversion logic (in a language
  40. other than CMakeLang), and then converting back to a string for any potential
  41. output.
  42. We can create a variable, which is to say a name for a string, using the
  43. :command:`set` command.
  44. .. code-block:: cmake
  45. set(var "World!")
  46. A variable's value can be accessed using brace expansion, for example if we want
  47. to use the :command:`message` command to print the string named by ``var``.
  48. .. code-block:: cmake
  49. set(var "World!")
  50. message("Hello ${var}")
  51. .. code-block:: console
  52. $ cmake -P CMakeLists.txt
  53. Hello World!
  54. .. note::
  55. :option:`cmake -P` is called "script mode", it informs CMake this file is not
  56. intended to have a :command:`project` command. We're not building any
  57. software, instead using CMake only as a command interpreter.
  58. Because CMakeLang has only strings, conditionals are entirely by convention of
  59. which strings are considered true and which are considered false. These are
  60. *supposed* to be intuitive, "True", "On", "Yes", and (strings representing)
  61. non-zero numbers are truthy, while "False" "Off", "No", "0", "Ignore",
  62. "NotFound", and the empty string are all considered false.
  63. However, some of the rules are more complex than that, so taking some time
  64. to consult the :command:`if` documentation on expressions is worthwhile. It's
  65. recommended to stick to a single pair for a given context, such as
  66. "True"/"False" or "On"/"Off".
  67. As mentioned, lists are strings containing semicolons. The :command:`list`
  68. command is useful for manipulating these, and many structures within CMake
  69. expect to operate with this convention. As an example, we can use the
  70. :command:`foreach` command to iterate over a list.
  71. .. code-block:: cmake
  72. set(stooges "Moe;Larry")
  73. list(APPEND stooges "Curly")
  74. message("Stooges contains: ${stooges}")
  75. foreach(stooge IN LISTS stooges)
  76. message("Hello, ${stooge}")
  77. endforeach()
  78. .. code-block:: console
  79. $ cmake -P CMakeLists.txt
  80. Stooges contains: Moe;Larry;Curly
  81. Hello, Moe
  82. Hello, Larry
  83. Hello, Curly
  84. Exercise 1 - Macros, Functions, and Lists
  85. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  86. CMake allows us to craft our own functions and macros. This can be very helpful
  87. when constructing lots of similar targets, like tests, for which we will want
  88. to call similar sets of commands over and over again. We do so with
  89. :command:`function` and :command:`macro`.
  90. .. code-block:: cmake
  91. macro(MyMacro MacroArgument)
  92. message("${MacroArgument}\n\t\tFrom Macro")
  93. endmacro()
  94. function(MyFunc FuncArgument)
  95. MyMacro("${FuncArgument}\n\tFrom Function")
  96. endfunction()
  97. MyFunc("From TopLevel")
  98. .. code-block:: console
  99. $ cmake -P CMakeLists.txt
  100. From TopLevel
  101. From Function
  102. From Macro
  103. Like with many languages, the difference between functions and macros is one
  104. of scope. In CMakeLang, both :command:`function` and :command:`macro` can "see"
  105. all the variables created in all the frames above them. However, a
  106. :command:`macro` acts semantically like a text replacement, similar to C/C++
  107. macros, so any side effects the macro creates are visible in their calling
  108. context. If we create or change a variable in a macro, the caller will see the
  109. change.
  110. :command:`function` creates its own variable scope, so side effects are not
  111. visible to the caller. In order to propagate changes to the parent which called
  112. the function, we must use ``set(<var> <value> PARENT_SCOPE)``, which works the
  113. same as :command:`set` but for variables belonging to the caller's context.
  114. .. note::
  115. In CMake 3.25, the :command:`return(PROPAGATE)` option was added, which
  116. works the same as :command:`set(PARENT_SCOPE)` but provides slightly better
  117. ergonomics.
  118. While not necessary for this exercise, it bears mentioning that :command:`macro`
  119. and :command:`function` both support variadic arguments via the ``ARGV``
  120. variable, a list containing all arguments passed to the command, and the
  121. ``ARGN`` variable, containing all arguments past the last expected argument.
  122. We're not going to build any targets in this exercise, so instead we'll
  123. construct our own version of :command:`list(APPEND)`, which adds a value to a
  124. list.
  125. Goal
  126. ----
  127. Implement a macro and a function which append a value to a list, without using
  128. the :command:`list(APPEND)` command.
  129. The desired usage of these commands is as follows:
  130. .. code-block:: cmake
  131. set(Letters "Alpha;Beta")
  132. MacroAppend(Letters "Gamma")
  133. message("Letters contains: ${Letters}")
  134. .. code-block:: console
  135. $ cmake -P Exercise1.cmake
  136. Letters contains: Alpha;Beta;Gamma
  137. .. note::
  138. The extension for these exercises is ``.cmake``, that's the standard extension
  139. for CMakeLang files when not contained in a ``CMakeLists.txt``
  140. Helpful Resources
  141. -----------------
  142. * :command:`macro`
  143. * :command:`function`
  144. * :command:`set`
  145. * :command:`if`
  146. Files to Edit
  147. -------------
  148. * ``Exercise1.cmake``
  149. Getting Started
  150. ----------------
  151. The source code for ``Exercise1.cmake`` is provided in the
  152. ``Help/guide/tutorial/Step2`` directory. It contains tests to verify the
  153. append behavior described above.
  154. .. note::
  155. You're not expected to handle the case of an empty or undefined list to
  156. append to. However, as a bonus, the case is tested if you want to try out
  157. your understanding of CMakeLang conditionals.
  158. Complete ``TODO 1`` and ``TODO 2``.
  159. Build and Run
  160. -------------
  161. We're going to use script mode to run these exercises. First navigate to the
  162. ``Help/guide/tutorial/Step2`` folder then you can run the code with:
  163. .. code-block:: console
  164. cmake -P Exercise1.cmake
  165. The script will report if the commands were implemented correctly.
  166. Solution
  167. --------
  168. This problem relies on an understanding of the mechanisms of CMake variables.
  169. CMake variables are names for strings; or put another way, a CMake variable
  170. is itself a string which can brace expand into a different string.
  171. This leads to a common pattern in CMake code where functions and macros aren't
  172. passed values, but rather, they are passed the names of variables which contain
  173. those values. Thus ``ListVar`` does not contain the *value* of the list we need
  174. to append to, it contains the *name* of a list, which contains the value we
  175. need to append to.
  176. When expanding the variable with ``${ListVar}``, we will get the name of the
  177. list. If we expand that name with ``${${ListVar}}``, we will get the values
  178. the list contains.
  179. To implement ``MacroAppend``, we need only combine this understanding of
  180. ``ListVar`` with our knowledge of the :command:`set` command.
  181. .. raw:: html
  182. <details><summary>TODO 1: Click to show/hide answer</summary>
  183. .. code-block:: cmake
  184. :caption: TODO 1: Exercise1.cmake
  185. :name: Exercise1.cmake-MacroAppend
  186. macro(MacroAppend ListVar Value)
  187. set(${ListVar} "${${ListVar}};${Value}")
  188. endmacro()
  189. .. raw:: html
  190. </details>
  191. We don't need to worry about scope here, because a macro operates in the same
  192. scope as its parent.
  193. ``FuncAppend`` is almost identical, in fact it could be implemented in the
  194. same one liner but with an added ``PARENT_SCOPE``, but the instructions ask
  195. us to implement it in terms of ``MacroAppend``.
  196. .. raw:: html
  197. <details><summary>TODO 2: Click to show/hide answer</summary>
  198. .. code-block:: cmake
  199. :caption: TODO 2: Exercise1.cmake
  200. :name: Exercise1.cmake-FuncAppend
  201. function(FuncAppend ListVar Value)
  202. MacroAppend(${ListVar} ${Value})
  203. set(${ListVar} "${${ListVar}}" PARENT_SCOPE)
  204. endfunction()
  205. .. raw:: html
  206. </details>
  207. ``MacroAppend`` transforms ``ListVar`` for us, but it won't propagate the result
  208. to the parent scope. Because this is a function, we need to do so ourselves
  209. with :command:`set(PARENT_SCOPE)`.
  210. Exercise 2 - Conditionals and Loops
  211. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  212. The two most common flow control elements in any structured programming
  213. language are conditionals and their close sibling loops. CMakeLang is no
  214. different. As previously mentioned, the truthiness of a given CMake string is a
  215. convention established by the :command:`if` command.
  216. When given a string, :command:`if` will first check if it is one of the known
  217. constant values previously discussed. If the string isn't one of those values
  218. the command assumes it is a variable, and checks the brace-expanded contents of
  219. that variable to determine the result of the conditional.
  220. .. code-block:: cmake
  221. if(True)
  222. message("Constant Value: True")
  223. else()
  224. message("Constant Value: False")
  225. endif()
  226. if(ConditionalValue)
  227. message("Undefined Variable: True")
  228. else()
  229. message("Undefined Variable: False")
  230. endif()
  231. set(ConditionalValue True)
  232. if(ConditionalValue)
  233. message("Defined Variable: True")
  234. else()
  235. message("Defined Variable: False")
  236. endif()
  237. .. code-block:: console
  238. $ cmake -P ConditionalValue.cmake
  239. Constant Value: True
  240. Undefined Variable: False
  241. Defined Variable: True
  242. .. note::
  243. This is a good a time as any to discuss quoting in CMake. All objects in
  244. CMake are strings, thus the double quote, ``"``, is often unnecessary.
  245. CMake knows the object is a string, everything is a string.
  246. However, it is needed in some contexts. Strings containing whitespace require
  247. double quotes, else they are treated like lists; CMake will concatenate the
  248. elements together with semicolons. The reverse is also true, when
  249. brace-expanding lists it is necessary to do so inside quotes if we want to
  250. *preserve* the semicolons. Otherwise CMake will expand the list items into
  251. space-separate strings.
  252. A handful of commands, such as :command:`if`, recognize the difference
  253. between quoted and unquoted strings. :command:`if` will only check that the
  254. given string represents a variable when the string is unquoted.
  255. Finally, :command:`if` provides several useful comparison modes such as
  256. ``STREQUAL`` for string matching, ``DEFINED`` for checking the existence of
  257. a variable, and ``MATCHES`` for regular expression checks. It also supports the
  258. typical logical operators, ``NOT``, ``AND``, and ``OR``.
  259. In addition to conditionals CMake provides two loop structures,
  260. :command:`while`, which follows the same rules as :command:`if` for checking a
  261. loop variable, and the more useful :command:`foreach`, which iterates over lists
  262. of strings and was demonstrated in the `Background`_ section.
  263. For this exercise, we're going to use loops and conditionals to solve some
  264. simple problems. We'll be using the aforementioned ``ARGN`` variable from
  265. :command:`function` as the list to operate on.
  266. Goal
  267. ----
  268. Loop over a list, and return all the strings containing the string ``Foo``.
  269. .. note::
  270. Those who read the command documentation will be aware that this is
  271. :command:`list(FILTER)`, resist the temptation to use it.
  272. Helpful Resources
  273. -----------------
  274. * :command:`function`
  275. * :command:`foreach`
  276. * :command:`if`
  277. * :command:`list`
  278. Files to Edit
  279. -------------
  280. * ``Exercise2.cmake``
  281. Getting Started
  282. ----------------
  283. The source code for ``Exercise2.cmake`` is provided in the ``Help/guide/tutorial/Step2``
  284. directory. It contains tests to verify the append behavior described above.
  285. .. note::
  286. You should use the :command:`list(APPEND)` command this time to collect your
  287. final result into a list. The input can be consumed from the ``ARGN`` variable
  288. of the provided function.
  289. Complete ``TODO 3``.
  290. Build and Run
  291. -------------
  292. Navigate to the ``Help/guide/tutorial/Step2`` folder then you can run the code with:
  293. .. code-block:: console
  294. cmake -P Exercise2.cmake
  295. The script will report if the ``FilterFoo`` function was implemented correctly.
  296. Solution
  297. --------
  298. We need to do three things, loop over the ``ARGN`` list, check if a given
  299. item in that list matches ``"Foo"``, and if so append it to the ``OutVar``
  300. list.
  301. While there are a couple ways we could invoke :command:`foreach`, the
  302. recommended way is to allow the command to do the variable expansion for us
  303. via ``IN LISTS`` to access the ``ARGN`` list items.
  304. The :command:`if` comparison we need is ``MATCHES`` which will check if
  305. ``"FOO"`` exists in the item. All that remains is to append the item to the
  306. ``OutVar`` list. The trickiest part is remembering that ``OutVar`` *names* a
  307. list, it is not the list itself, so we need to access it via ``${OutVar}``.
  308. .. raw:: html
  309. <details><summary>TODO 3: Click to show/hide answer</summary>
  310. .. code-block:: cmake
  311. :caption: TODO 3: Exercise2.cmake
  312. :name: Exercise2.cmake-FilterFoo
  313. function(FilterFoo OutVar)
  314. foreach(item IN LISTS ARGN)
  315. if(item MATCHES Foo)
  316. list(APPEND ${OutVar} ${item})
  317. endif()
  318. endforeach()
  319. set(${OutVar} ${${OutVar}} PARENT_SCOPE)
  320. endfunction()
  321. .. raw:: html
  322. </details>
  323. Exercise 3 - Organizing with Include
  324. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  325. We have already discussed how to incorporate subdirectories containing their
  326. own CMLs with :command:`add_subdirectory`. In later steps we will explore
  327. the various way CMake code can be packaged and shared across projects.
  328. However for small CMake functions and utilities, it is often beneficial for them
  329. to live in their own ``.cmake`` files outside the project CMLs and separate
  330. from the rest of the build system. This allows for separation of concerns,
  331. removing the project-specific elements from the utilities we are using to
  332. describe them.
  333. To incorporate these separate ``.cmake`` files into our project, we use the
  334. :command:`include` command. This command immediately begins interpreting the
  335. contents of the :command:`include`'d file in the scope of the parent CML. It
  336. is as if the entire file were being called as a macro.
  337. Traditionally, these kinds of ``.cmake`` files live in a folder named "cmake"
  338. inside the project root. For this exercise, we'll use the ``Step2`` folder instead.
  339. Goal
  340. ----
  341. Use the functions from Exercises 1 and 2 to build and filter our own list of items.
  342. Helpful Resources
  343. -----------------
  344. * :command:`include`
  345. Files to Edit
  346. -------------
  347. * ``Exercise3.cmake``
  348. Getting Started
  349. ----------------
  350. The source code for ``Exercise3.cmake`` is provided in the ``Help/guide/tutorial/Step2``
  351. directory. It contains tests to verify the correct usage of our functions
  352. from the previous two exercises.
  353. .. note::
  354. Actually it reuses tests from Exercise2.cmake, reusable code is good for
  355. everyone.
  356. Complete ``TODO 4`` through ``TODO 7``.
  357. Build and Run
  358. -------------
  359. Navigate to the ``Help/guide/tutorial/Step2`` folder then you can run the code with:
  360. .. code-block:: console
  361. cmake -P Exercise3.cmake
  362. The script will report if the functions were invoked and composed correctly.
  363. Solution
  364. --------
  365. The :command:`include` command will interpret the included file completely,
  366. including the tests from the first two exercises. We don't want to run these
  367. tests again. Thanks to some forethought, these files check a variable called
  368. ``SKIP_TESTS`` prior to running their tests, setting this to ``True`` will
  369. get us the behavior we want.
  370. .. raw:: html
  371. <details><summary>TODO 4: Click to show/hide answer</summary>
  372. .. code-block:: cmake
  373. :caption: TODO 4: Exercise3.cmake
  374. :name: Exercise3.cmake-SKIP_TESTS
  375. set(SKIP_TESTS True)
  376. .. raw:: html
  377. </details>
  378. Now we're ready to :command:`include` the previous exercises to grab their
  379. functions.
  380. .. raw:: html
  381. <details><summary>TODO 5: Click to show/hide answer</summary>
  382. .. code-block:: cmake
  383. :caption: TODO 5: Exercise3.cmake
  384. :name: Exercise3.cmake-include
  385. include(Exercise1.cmake)
  386. include(Exercise2.cmake)
  387. .. raw:: html
  388. </details>
  389. Now that ``FuncAppend`` is available to us, we can use it to append new elements
  390. to the ``InList``.
  391. .. raw:: html
  392. <details><summary>TODO 6: Click to show/hide answer</summary>
  393. .. code-block:: cmake
  394. :caption: TODO 6: Exercise3.cmake
  395. :name: Exercise3.cmake-FuncAppend
  396. FuncAppend(InList FooBaz)
  397. FuncAppend(InList QuxBaz)
  398. .. raw:: html
  399. </details>
  400. Finally, we can use ``FilterFoo`` to filter the full list. The tricky part to
  401. remember here is that our ``FilterFoo`` wants to operate on list values via
  402. ``ARGN``, so we need to expand the ``InList`` when we call ``FilterFoo``.
  403. .. raw:: html
  404. <details><summary>TODO 7: Click to show/hide answer</summary>
  405. .. code-block:: cmake
  406. :caption: TODO 7: Exercise3.cmake
  407. :name: Exercise3.cmake-FilterFoo
  408. FilterFoo(OutList ${InList})
  409. .. raw:: html
  410. </details>