File: Configuration%20and%20Cache%20Variables.rst

package info (click to toggle)
cmake 4.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 152,348 kB
  • sloc: ansic: 403,894; cpp: 303,807; sh: 4,097; python: 3,582; yacc: 3,106; lex: 1,279; f90: 538; asm: 471; lisp: 375; cs: 270; java: 266; fortran: 239; objc: 215; perl: 213; xml: 198; makefile: 108; javascript: 83; pascal: 63; tcl: 55; php: 25; ruby: 22
file content (606 lines) | stat: -rw-r--r-- 18,395 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
Step 3: Configuration and Cache Variables
=========================================

CMake projects often have some project-specific configuration variables which
users and packagers are interested in. CMake has many ways that an invoking
user or process can communicate these configuration choices, but the most
fundamental of them are :option:`-D <cmake -D>` flags.

In this step we'll explore the ins and out of how to provide project
configuration options from within a CML, and how to invoke CMake to take
advantage of configuration options provided by both CMake and individual
projects.

Background
^^^^^^^^^^

If we had a CMake project for compression software which supported multiple
compression algorithms, we might want to let the packager of the project decide
which algorithms to enable when they build our software. We can do so by
consuming variables set via :option:`-D <cmake -D>` flags.

.. code-block:: cmake

  if(COMPRESSION_SOFTWARE_USE_ZLIB)
    message("I will use Zlib!")
    # ...
  endif()

  if(COMPRESSION_SOFTWARE_USE_ZSTD)
    message("I will use Zstd!")
    # ...
  endif()

.. code-block:: console

  $ cmake -B build \
      -DCOMPRESSION_SOFTWARE_USE_ZLIB=ON \
      -DCOMPRESSION_SOFTWARE_USE_ZSTD=OFF
  ...
  I will use Zlib!

Of course, we will want to provide reasonable defaults for these configuration
choices, and a way to communicate the purpose of a given option. This function
is provided by the :command:`option` command.

.. code-block:: cmake

  option(COMPRESSION_SOFTWARE_USE_ZLIB "Support Zlib compression" ON)
  option(COMPRESSION_SOFTWARE_USE_ZSTD "Support Zstd compression" ON)

  if(COMPRESSION_SOFTWARE_USE_ZLIB)
    # Same as before
  # ...

.. code-block:: console

  $ cmake -B build \
      -DCOMPRESSION_SOFTWARE_USE_ZLIB=OFF
  ...
  I will use Zstd!

The names created by :option:`-D <cmake -D>` flags and :command:`option` are
not normal variables, they are **cache** variables. Cache variables are globally
visible variables which are *sticky*, their value is difficult to change after
it is initially set. In fact they are so sticky that, in project mode, CMake
will save and restore cache variables across multiple configurations. If a
cache variable is set once, it will remain until another :option:`-D <cmake -D>`
flag preempts the saved variable.

.. note::
  CMake itself has dozens of normal and cache variables used for configuration.
  These are documented at :manual:`cmake-variables(7)` and operate in the same
  manner as project-provided variables for configuration.

:command:`set` can also be used to manipulate cache variables, but will not
change a variable which has already been created.

.. code-block:: cmake

  set(StickyCacheVariable "I will not change" CACHE STRING "")
  set(StickyCacheVariable "Overwrite StickyCache" CACHE STRING "")

  message("StickyCacheVariable: ${StickyCacheVariable}")

.. code-block:: console

  $ cmake -P StickyCacheVariable.cmake
  StickyCacheVariable: I will not change

Because :option:`-D <cmake -D>` flags are processed before any other commands,
they take precedence for setting the value of a cache variable.

.. code-block:: console

  $ cmake \
    -DStickyCacheVariable="Commandline always wins" \
    -P StickyCacheVariable.cmake
  StickyCacheVariable: Commandline always wins

While cache variables cannot ordinarily be changed, they can be *shadowed* by
normal variables. We can observe this by :command:`set`'ing a variable to have
the same name as a cache variable, and then using :command:`unset` to remove
the normal variable.

.. code-block:: cmake

  set(ShadowVariable "In the shadows" CACHE STRING "")
  set(ShadowVariable "Hiding the cache variable")
  message("ShadowVariable: ${ShadowVariable}")

  unset(ShadowVariable)
  message("ShadowVariable: ${ShadowVariable}")

.. code-block:: console

  $ cmake -P ShadowVariable.cmake
  ShadowVariable: Hiding the cache variable
  ShadowVariable: In the shadows

Exercise 1 - Using Options
^^^^^^^^^^^^^^^^^^^^^^^^^^

We can imagine a scenario where consumers really want our ``MathFunctions``
library, and the ``Tutorial`` utility is a "take it or leave it" add-on. In
that case, we might want to add an option to allow consumers to disable
building our ``Tutorial`` binary, building only the ``MathFunctions`` library.

With our knowledge of options, conditionals, and cache variables we have all
the pieces we need to make this configuration available.

Goal
----

Add an option named ``TUTORIAL_BUILD_UTILITIES`` to control if the ``Tutorial``
binary is configured and built.

.. note::
  CMake allows us to determine which targets are built after configuration. Our
  users could ask for the ``MathFunctions`` library alone without ``Tutorial``.
  CMake also has mechanisms to exclude targets from ``ALL``, the default target
  which builds all the other available targets.

  However, options which completely exclude targets from the configuration are
  convenient and popular, especially if configuring those targets involves
  heavy-weight steps which might take some time.

  It also simplifies :command:`install()` logic, which we'll discuss in later
  steps, if targets the packager is uninterested in are completely excluded.

Helpful Resources
-----------------

* :command:`option`
* :command:`if`

Files to Edit
-------------

* ``CMakeLists.txt``

Getting Started
---------------

The ``Help/guide/tutorial/Step3`` folder contains the complete, recommended
solution to ``Step1`` and the relevant ``TODOs`` for this step. Take a minute
to review and refamiliarize yourself with the ``Tutorial`` project.

When you feel you have an understanding of the current code, start with
``TODO 1`` and complete through ``TODO 2``.

Build and Run
-------------

We can now reconfigure our project. However, this time we want to control the
configuration via :option:`-D <cmake -D>` flags. We again start by navigating
to ``Help/guide/tutorial/Step3`` and invoking CMake, but this time with our
configuration options.

.. code-block:: console

  cmake -B build -DTUTORIAL_BUILD_UTILITIES=OFF

We can now build as usual.

.. code-block:: console

  cmake --build build

After the build we should observe no Tutorial executable is produced. Because
cache variables are sticky even a reconfigure shouldn't change this, despite
the default-``ON`` option.

.. code-block:: console

  cmake -B build
  cmake --build build

Will not produce the Tutorial executable, the cache variables are "locked in".
To change this we have two options. First, we can edit the file which stores
the cache variables between CMake configuration runs, the "CMake Cache". This
file is ``build/CMakeCache.txt``, in it we can find the option cache variable.

.. code-block:: text

  //Build the Tutorial executable
  TUTORIAL_BUILD_UTILITIES:BOOL=OFF

We can change this from ``OFF`` to ``ON``, rerun the build, and we will get
our ``Tutorial`` executable.

.. note::
  ``CMakeCache.txt`` entries are of the form ``<Name>:<Type>=<Value>``, however
  the "type" is only a hint. All objects in CMake are strings, regardless of
  what the cache says.

Alternatively, we can change the value of the cache variable on the command
line, because the command line runs before ``CMakeCache.txt`` is loaded its
value take precedence over those in the cache file.

.. code-block:: console

  cmake -B build -DTUTORIAL_BUILD_UTILITIES=ON
  cmake --build build

Doing so we observe the value in ``CMakeCache.txt`` has flipped from ``OFF``
to ``ON``, and that the ``Tutorial`` executable is built.

Solution
--------

First we create our :command:`option` to provide our cache variable with a
reasonable default value.

.. raw:: html

  <details><summary>TODO 1: Click to show/hide answer</summary>

.. literalinclude:: Step4/CMakeLists.txt
  :caption: TODO 1: CMakeLists.txt
  :name: CMakeLists.txt-option-TUTORIAL_BUILD_UTILITIES
  :language: cmake
  :start-at: option(TUTORIAL_BUILD_UTILITIES
  :end-at: option(TUTORIAL_BUILD_UTILITIES

.. raw:: html

  </details>

Then we can check the cache variable to conditionally enable the ``Tutorial``
executable (by way of adding its subdirectory).

.. raw:: html

  <details><summary>TODO 2: Click to show/hide answer</summary>

.. literalinclude:: Step4/CMakeLists.txt
  :caption: TODO 2: CMakeLists.txt
  :name: CMakeLists.txt-if-TUTORIAL_BUILD_UTILITIES
  :language: cmake
  :start-at: if(TUTORIAL_BUILD_UTILITIES)
  :end-at: endif()

.. raw:: html

  </details>

Exercise 2 - ``CMAKE`` Variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

CMake has several important normal and cache variables provided to allow
packagers to control the build. Decisions such as compilers, default flags,
search locations for packages, and much more are all controlled by CMake's
own configuration variables.

Among the most important are language standards. As the language standard can
have significant impact on the ABI presented by a given package. For example,
it's quite common for libraries to use standard C++ templates on later
standards, and provide polyfills on earlier standards. If a library is consumed
under different standards then ABI incompatibilities between the standard
templates and the polyfills can result in incomprehensible errors and runtime
crashes.

Ensuring all of our targets are built under the same language standard is
achieved with the :variable:`CMAKE_<LANG>_STANDARD` cache variables. For C++,
this is ``CMAKE_CXX_STANDARD``.

.. note::
  Because these variables are so important, it is equally important that
  developers not override or shadow them in their CMLs. Shadowing
  :variable:`CMAKE_<LANG>_STANDARD` in a CML because the library wants C++20,
  when the packager has decided to build the rest of their libraries and
  applications with C++23, can lead to the aforementioned terrible,
  incomprehensible errors.

  Do not :command:`set` ``CMAKE_`` globals without very strong reasons for
  doing so. We'll discuss better methods for targets to communicate
  requirements like definitions and minimum standards in later steps.

In this exercise, we'll introduce some C++20 code into our library and
executable and build them with C++20 by setting the appropriate cache variable.

Goal
----

Use ``std::format`` to format printed strings instead of stream operators. To
ensure availability of ``std::format``, configure CMake to use the C++20
standard for C++ targets.

Helpful Resources
-----------------

* :option:`cmake -D`
* :variable:`CMAKE_<LANG>_STANDARD`
* :variable:`CMAKE_CXX_STANDARD`
* :prop_tgt:`CXX_STANDARD`
* `cppreference \<format\> <https://en.cppreference.com/w/cpp/utility/format/format.html>`_

Files to Edit
-------------

* ``Tutorial/Tutorial.cxx``
* ``MathFunctions/MathFunctions.cxx``

Getting Started
---------------

Continue to edit files from ``Step3``. Complete ``TODO 3`` through ``TODO 7``.
We'll be modifying our prints to use ``std::format`` instead of stream
operators.

Ensure your cache variables are set such that the Tutorial executable will be
built, using any of the methods discussed in the previous exercise.

Build and Run
-------------

We need to reconfigure our project with the new standard, we can do this
using the same method as our ``TUTORIAL_BUILD_UTILITIES`` cache variable.

.. code-block:: console

  cmake -B build -DCMAKE_CXX_STANDARD=20

.. note::
  Configuration variables are, by convention, prefixed with the provider of the
  variable. CMake configuration variables are prefixed with ``CMAKE_``, while
  projects should prefix their variables with ``<PROJECT>_``.

  The tutorial configuration variables follow this convention, and are prefixed
  with ``TUTORIAL_``.

Now that we've configured with C++20, we can build as usual.

.. code-block:: console

  cmake --build build

Solution
--------

We need to include ``<format>`` and then use it.

.. raw:: html

  <details><summary>TODO 3-5: Click to show/hide answer</summary>

.. literalinclude:: Step4/Tutorial/Tutorial.cxx
  :caption: TODO 3: Tutorial/Tutorial.cxx
  :name: Tutorial/Tutorial.cxx-include-format
  :language: c++
  :start-at: #include <format>
  :end-at: #include <string>

.. literalinclude:: Step4/Tutorial/Tutorial.cxx
  :caption: TODO 4: Tutorial/Tutorial.cxx
  :name: Tutorial/Tutorial.cxx-format1
  :language: c++
  :start-at: if (argc < 2) {
  :end-at: return 1;
  :append: }
  :dedent: 2

.. literalinclude:: Step4/Tutorial/Tutorial.cxx
  :caption: TODO 5: Tutorial/Tutorial.cxx
  :name: Tutorial/Tutorial.cxx-format3
  :language: c++
  :start-at: // calculate square root
  :end-at: outputValue);
  :dedent: 2

.. raw:: html

  </details>

And again for the ``MathFunctions`` library.

.. raw:: html

  <details><summary>TODO 6-7: Click to show/hide answer</summary>

.. literalinclude:: Step4/MathFunctions/MathFunctions.cxx
  :caption: TODO 6: MathFunctions.cxx
  :name: MathFunctions/MathFunctions.cxx-include-format
  :language: c++
  :start-at: #include <format>
  :end-at: #include <iostream>

.. literalinclude:: Step4/MathFunctions/MathFunctions.cxx
  :caption: TODO 7: MathFunctions.cxx
  :name: MathFunctions/MathFunctions.cxx-format
  :language: c++
  :start-at: double delta
  :end-at: std::format
  :dedent: 4

.. raw:: html

  </details>

Exercise 3 - CMakePresets.json
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Managing these configuration values can quickly become overwhelming. In CI
systems it is appropriate to record these as part of a given CI step. For
example in a Github Actions CI step we might see something akin to the
following:

.. code-block:: yaml

  - name: Configure and Build
    run: |
      cmake \
        -B build \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_CXX_STANDARD=20 \
        -DCMAKE_CXX_EXTENSIONS=ON \
        -DTUTORIAL_BUILD_UTILITIES=OFF \
        # Possibly many more options
        # ...

      cmake --build build

When developing code locally, typing all these options even once might be error
prone. If a fresh configuration is needed for any reason, doing so multiple
times could be exhausting.

There are many and varied solutions to this problem, and your choice is
ultimately up to your preferences as a developer. CLI-oriented developers
commonly use task runners to invoke CMake with their desired options for a
project. Most IDEs also have a custom mechanism for controlling CMake
configuration.

It would be impossible to fully enumerate every possible configuration workflow
here. Instead we will explore CMake's built-in solution, known as
:manual:`CMake Presets <cmake-presets(7)>`. Presets give us a format to name
and express collections of CMake configuration options.

.. note::
    Presets are capable of expressing entire CMake workflows, from
    configuration, through building, all the way to installing the software
    package.

    They are far more flexible than can we have room for here. We'll limit
    ourselves to using them for configuration.

CMake Presets come in two standard files, ``CMakePresets.json``, which is
intended to be a part of the project and tracked in source control; and
``CMakeUserPresets.json``, which is intended for local user configuration
and should not be tracked in source control.

The simplest preset which would be of use to a developer does nothing more
than configure variables.

.. code-block:: json

  {
    "version": 4,
    "configurePresets": [
      {
        "name": "example-preset",
        "cacheVariables": {
          "EXAMPLE_FOO": "Bar",
          "EXAMPLE_QUX": "Baz"
        }
      }
    ]
  }

When invoking CMake, where previously we would have done:

.. code-block:: console

  cmake -B build -DEXAMPLE_FOO=Bar -DEXAMPLE_QUX=Baz

We can now use the preset:

.. code-block:: console

  cmake -B build --preset example-preset

CMake will search for files named ``CMakePresets.json`` and
``CMakeUserPresets.json``, and load the named configuration from them if
available.

.. note::
  Command line flags can be mixed with presets. Command line flags have
  precedence over values found in a preset.

Presets also support limited macros, variables that can be brace-expanded
inside the preset. The only one of interest to us is the ``${sourceDir}`` macro,
which expands to the root directory of the project. We can use this to set our
build directory, skipping the :option:`-B <cmake -B>` flag when configuring
the project.

.. code-block:: json

  {
    "name": "example-preset",
    "binaryDir": "${sourceDir}/build"
  }

Goal
----

Configure and build the tutorial using a CMake Preset instead of command line
flags.

Helpful Resources
-----------------

* :manual:`cmake-presets(7)`

Files to Edit
-------------

* ``CMakePresets.json``

Getting Started
---------------

Continue to edit files from ``Step3``. Complete ``TODO 8`` and ``TODO 9``.

.. note::
  ``TODOs`` inside ``CMakePresets.json`` need to be *replaced*. There should
  be no ``TODO`` keys left inside the file when you have completed the exercise.

You can verify the preset is working correctly by deleting the existing build
folder before you configure, this will ensure you're not reusing the existing
CMake Cache for configuration.

.. note::
  On CMake 3.24 and newer, the same effect can be achieved by configuring with
  :option:`cmake --fresh`.

All future configuration changes will be via the ``CMakePresets.json`` file.

Build and Run
-------------

We can now use the preset file to manage our configuration.

.. code-block:: console

  cmake --preset tutorial

Presets are capable of running the build step for us, but for this tutorial
we'll continue to run the build ourselves.

.. code-block:: console

  cmake --build build

Solution
--------

There are two changes we need to make, first we want to set the build
directory (also called the "binary directory") to the ``build`` subdirectory
of our project folder, and second we need to set the ``CMAKE_CXX_STANDARD`` to
``20``.

.. raw:: html

  <details><summary>TODO 8-9: Click to show/hide answer</summary>

.. code-block:: json
  :caption: TODO 8-9: CMakePresets.json
  :name: CMakePresets.json-initial

  {
    "version": 4,
    "configurePresets": [
      {
        "name": "tutorial",
        "displayName": "Tutorial Preset",
        "description": "Preset to use with the tutorial",
        "binaryDir": "${sourceDir}/build",
        "cacheVariables": {
          "CMAKE_CXX_STANDARD": "20"
        }
      }
    ]
  }

.. raw:: html

  </details>