mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-21 14:40:48 +08:00
607 lines
18 KiB
ReStructuredText
607 lines
18 KiB
ReStructuredText
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>
|