Tutorial: Rewrite using conventions enabled by CMake 3.23

This is a full re-write of the CMake Tutorial for CMake 3.23, both
the functionality it provides, as well as the modern workflows that
developers use when interfacing with CMake.

Issue: #22663, #23086, #23799, #26053, #26105, #26153, #26914
This commit is contained in:
Vito Gamberini
2025-08-20 12:42:42 -04:00
committed by Brad King
parent 9e89400d13
commit b2e3e3e30e
361 changed files with 10167 additions and 4863 deletions

View File

@@ -1,473 +0,0 @@
Step 1: A Basic Starting Point
==============================
Where do I start with CMake? This step will provide an introduction to some of
CMake's basic syntax, commands, and variables. As these concepts are
introduced, we will work through three exercises and create a simple CMake
project.
Each exercise in this step will start with some background information. Then, a
goal and list of helpful resources are provided. Each file in the
``Files to Edit`` section is in the ``Step1`` directory and contains one or
more ``TODO`` comments. Each ``TODO`` represents a line or two of code to
change or add. The ``TODO`` s are intended to be completed in numerical order,
first complete ``TODO 1`` then ``TODO 2``, etc. The ``Getting Started``
section will give some helpful hints and guide you through the exercise. Then
the ``Build and Run`` section will walk step-by-step through how to build and
test the exercise. Finally, at the end of each exercise the intended solution
is discussed.
Also note that each step in the tutorial builds on the previous. For example,
the starting code for ``Step2`` is the complete solution to ``Step1``.
Exercise 1 - Building a Basic Project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The most basic CMake project is an executable built from a single source code
file. For simple projects like this, a ``CMakeLists.txt`` file with three
commands is all that is required.
**Note:** Although upper, lower and mixed case commands are supported by CMake,
lower case commands are preferred and will be used throughout the tutorial.
Any project's top most CMakeLists.txt must start by specifying a minimum CMake
version using the :command:`cmake_minimum_required` command. This establishes
policy settings and ensures that the following CMake functions are run with a
compatible version of CMake.
To start a project, we use the :command:`project` command to set the project
name. This call is required with every project and should be called soon after
:command:`cmake_minimum_required`. As we will see later, this command can
also be used to specify other project level information such as the language
or version number.
Finally, the :command:`add_executable` command tells CMake to create an
executable using the specified source code files.
Goal
----
Understand how to create a simple CMake project.
Helpful Resources
-----------------
* :command:`add_executable`
* :command:`cmake_minimum_required`
* :command:`project`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
----------------
The source code for ``tutorial.cxx`` is provided in the
``Help/guide/tutorial/Step1`` directory and can be used to compute the square
root of a number. This file does not need to be edited in this step.
In the same directory is a ``CMakeLists.txt`` file which you will complete.
Start with ``TODO 1`` and work through ``TODO 3``.
Build and Run
-------------
Once ``TODO 1`` through ``TODO 3`` have been completed, we are ready to build
and run our project! First, run the :manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool.
For example, from the command line we could navigate to the
``Help/guide/tutorial`` directory of the CMake source code tree and create a
build directory:
.. code-block:: console
mkdir Step1_build
Next, navigate to that build directory and run
:manual:`cmake <cmake(1)>` to configure the project and generate a native build
system:
.. code-block:: console
cd Step1_build
cmake ../Step1
Then call that build system to actually compile/link the project:
.. code-block:: console
cmake --build .
For multi-config generators (e.g. Visual Studio), first navigate to the
appropriate subdirectory, for example:
.. code-block:: console
cd Debug
Finally, try to use the newly built ``Tutorial``:
.. code-block:: console
Tutorial 4294967296
Tutorial 10
Tutorial
**Note:** Depending on the shell, the correct syntax may be ``Tutorial``,
``./Tutorial`` or ``.\Tutorial``. For simplicity, the exercises will use
``Tutorial`` throughout.
Solution
--------
As mentioned above, a three line ``CMakeLists.txt`` is all that we need to get
up and running. The first line is to use :command:`cmake_minimum_required` to
set the CMake version as follows:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 1: CMakeLists.txt
:name: CMakeLists.txt-cmake_minimum_required
:language: cmake
:end-before: # set the project name and version
.. raw:: html
</details>
The next step to make a basic project is to use the :command:`project`
command as follows to set the project name:
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-project
project(Tutorial)
.. raw:: html
</details>
The last command to call for a basic project is
:command:`add_executable`. We call it as follows:
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-add_executable
:language: cmake
:start-after: # add the executable
:end-before: # TODO 3:
.. raw:: html
</details>
Exercise 2 - Specifying the C++ Standard
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CMake has some special variables that are either created behind the scenes or
have meaning to CMake when set by project code. Many of these variables start
with ``CMAKE_``. Avoid this naming convention when creating variables for your
projects. Two of these special user settable variables are
:variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`.
These may be used together to specify the C++ standard needed to build the
project.
Goal
----
Add a feature that requires C++11.
Helpful Resources
-----------------
* :variable:`CMAKE_CXX_STANDARD`
* :variable:`CMAKE_CXX_STANDARD_REQUIRED`
* :command:`set`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``tutorial.cxx``
Getting Started
---------------
Continue editing files in the ``Step1`` directory. Start with ``TODO 4`` and
complete through ``TODO 6``.
First, edit ``tutorial.cxx`` by adding a feature that requires C++11. Then
update ``CMakeLists.txt`` to require C++11.
Build and Run
-------------
Let's build our project again. Since we already created a build directory and
ran CMake for Exercise 1, we can skip to the build step:
.. code-block:: console
cd Step1_build
cmake --build .
Now we can try to use the newly built ``Tutorial`` with same commands as
before:
.. code-block:: console
Tutorial 4294967296
Tutorial 10
Tutorial
Solution
--------
We start by adding some C++11 features to our project by replacing
``atof`` with ``std::stod`` in ``tutorial.cxx``. This looks like
the following:
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. literalinclude:: Step2/tutorial.cxx
:caption: TODO 4: tutorial.cxx
:name: tutorial.cxx-cxx11
:language: c++
:start-after: // convert input to double
:end-before: // TODO 6:
.. raw:: html
</details>
To complete ``TODO 5``, simply remove ``#include <cstdlib>``.
We will need to explicitly state in the CMake code that it should use the
correct flags. One way to enable support for a specific C++ standard in CMake
is by using the :variable:`CMAKE_CXX_STANDARD` variable. For this tutorial, set
the :variable:`CMAKE_CXX_STANDARD` variable in the ``CMakeLists.txt`` file to
``11`` and :variable:`CMAKE_CXX_STANDARD_REQUIRED` to ``True``. Make sure to
add the :variable:`CMAKE_CXX_STANDARD` declarations above the call to
:command:`add_executable`.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 6: CMakeLists.txt
:name: CMakeLists.txt-CXX_STANDARD
:language: cmake
:start-after: # specify the C++ standard
:end-before: # configure a header file
.. raw:: html
</details>
Exercise 3 - Adding a Version Number and Configured Header File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes it may be useful to have a variable that is defined in your
``CMakelists.txt`` file also be available in your source code. In this case, we
would like to print the project version.
One way to accomplish this is by using a configured header file. We create an
input file with one or more variables to replace. These variables have special
syntax which looks like ``@VAR@``.
Then, we use the :command:`configure_file` command to copy the input file to a
given output file and replace these variables with the current value of ``VAR``
in the ``CMakelists.txt`` file.
While we could edit the version directly in the source code, using this
feature is preferred since it creates a single source of truth and avoids
duplication.
Goal
----
Define and report the project's version number.
Helpful Resources
-----------------
* :variable:`<PROJECT-NAME>_VERSION_MAJOR`
* :variable:`<PROJECT-NAME>_VERSION_MINOR`
* :command:`configure_file`
* :command:`target_include_directories`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``tutorial.cxx``
* ``TutorialConfig.h.in``
Getting Started
---------------
Continue to edit files from ``Step1``. Start on ``TODO 7`` and complete through
``TODO 12``. In this exercise, we start by adding a project version number in
``CMakeLists.txt``. In that same file, use :command:`configure_file` to copy a
given input file to an output file and substitute some variable values in the
input file content.
Next, create an input header file ``TutorialConfig.h.in`` defining version
numbers which will accept variables passed from :command:`configure_file`.
Finally, update ``tutorial.cxx`` to print out its version number.
Build and Run
-------------
Let's build our project again. As before, we already created a build directory
and ran CMake so we can skip to the build step:
.. code-block:: console
cd Step1_build
cmake --build .
Verify that the version number is now reported when running the executable
without any arguments.
Solution
--------
In this exercise, we improve our executable by printing a version number.
While we could do this exclusively in the source code, using ``CMakeLists.txt``
lets us maintain a single source of data for the version number.
First, we modify the ``CMakeLists.txt`` file to use the
:command:`project` command to set both the project name and version number.
When the :command:`project` command is called, CMake defines
``Tutorial_VERSION_MAJOR`` and ``Tutorial_VERSION_MINOR`` behind the scenes.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 7: CMakeLists.txt
:name: CMakeLists.txt-project-VERSION
:language: cmake
:start-after: # set the project name and version
:end-before: # specify the C++ standard
.. raw:: html
</details>
Then we used :command:`configure_file` to copy the input file with the
specified CMake variables replaced:
.. raw:: html
<details><summary>TODO 8: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 8: CMakeLists.txt
:name: CMakeLists.txt-configure_file
:language: cmake
:start-after: # to the source code
:end-before: # TODO 2:
.. raw:: html
</details>
Since the configured file will be written into the project binary
directory, we must add that directory to the list of paths to search for
include files.
**Note:** Throughout this tutorial, we will refer to the project build and
the project binary directory interchangeably. These are the same and are not
meant to refer to a ``bin/`` directory.
We used :command:`target_include_directories` to specify
where the executable target should look for include files.
.. raw:: html
<details><summary>TODO 9: Click to show/hide answer</summary>
.. literalinclude:: Step2/CMakeLists.txt
:caption: TODO 9: CMakeLists.txt
:name: CMakeLists.txt-target_include_directories
:language: cmake
:start-after: # so that we will find TutorialConfig.h
.. raw:: html
</details>
``TutorialConfig.h.in`` is the input header file to be configured.
When :command:`configure_file` is called from our ``CMakeLists.txt``, the
values for ``@Tutorial_VERSION_MAJOR@`` and ``@Tutorial_VERSION_MINOR@`` will
be replaced with the corresponding version numbers from the project in
``TutorialConfig.h``.
.. raw:: html
<details><summary>TODO 10: Click to show/hide answer</summary>
.. literalinclude:: Step2/TutorialConfig.h.in
:caption: TODO 10: TutorialConfig.h.in
:name: TutorialConfig.h.in
:language: c++
.. raw:: html
</details>
Next, we need to modify ``tutorial.cxx`` to include the configured header file,
``TutorialConfig.h``.
.. raw:: html
<details><summary>TODO 11: Click to show/hide answer</summary>
.. code-block:: c++
:caption: TODO 11: tutorial.cxx
#include "TutorialConfig.h"
.. raw:: html
</details>
Finally, we print out the executable name and version number by updating
``tutorial.cxx`` as follows:
.. raw:: html
<details><summary>TODO 12: Click to show/hide answer</summary>
.. literalinclude:: Step2/tutorial.cxx
:caption: TODO 12 : tutorial.cxx
:name: tutorial.cxx-print-version
:language: c++
:start-after: {
:end-before: // convert input to double
.. raw:: html
</details>

View File

@@ -1,140 +0,0 @@
Step 11: Adding Export Configuration
====================================
During :guide:`tutorial/Installing and Testing` of the tutorial we added the
ability for CMake to install the library and headers of the project. During
:guide:`tutorial/Packaging an Installer` we added the ability to package up
this information so it could be distributed to other people.
The next step is to add the necessary information so that other CMake projects
can use our project, be it from a build directory, a local install or when
packaged.
The first step is to update our :command:`install(TARGETS)` commands to not
only specify a ``DESTINATION`` but also an ``EXPORT``. The ``EXPORT`` keyword
generates a CMake file containing code to import all targets listed in the
install command from the installation tree. So let's go ahead and explicitly
``EXPORT`` the ``MathFunctions`` library by updating the ``install`` command
in ``MathFunctions/CMakeLists.txt`` to look like:
.. literalinclude:: Complete/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-install-TARGETS-EXPORT
:language: cmake
:start-after: # install libs
Now that we have ``MathFunctions`` being exported, we also need to explicitly
install the generated ``MathFunctionsTargets.cmake`` file. This is done by
adding the following to the bottom of the top-level ``CMakeLists.txt``:
.. literalinclude:: Complete/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-install-EXPORT
:language: cmake
:start-after: # install the configuration targets
:end-before: include(CMakePackageConfigHelpers)
At this point you should try and run CMake. If everything is setup properly
you will see that CMake will generate an error that looks like:
.. code-block:: console
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
CMake is telling you that during the generation of the export information
it will export a path that is intrinsically tied to the current machine and
will not be valid on other machines. The solution to this is to update the
``MathFunctions`` :command:`target_include_directories` to understand that it
needs different ``INTERFACE`` locations when being used from within the build
directory and from an install / package. This means converting the
:command:`target_include_directories` call for ``MathFunctions`` to look like:
.. literalinclude:: Step12/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-target_include_directories
:language: cmake
:start-after: # to find MathFunctions.h, while we don't.
:end-before: # should we use our own math functions
Once this has been updated, we can re-run CMake and verify that it doesn't
warn anymore.
At this point, we have CMake properly packaging the target information that is
required but we will still need to generate a ``MathFunctionsConfig.cmake`` so
that the CMake :command:`find_package` command can find our project. So let's go
ahead and add a new file to the top-level of the project called
``Config.cmake.in`` with the following contents:
.. literalinclude:: Step12/Config.cmake.in
:caption: Config.cmake.in
:name: Config.cmake.in
Then, to properly configure and install that file, add the following to the
bottom of the top-level ``CMakeLists.txt``:
.. literalinclude:: Step12/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-install-Config.cmake
:language: cmake
:start-after: # install the configuration targets
:end-before: # generate the config file
Next, we execute the :command:`configure_package_config_file`. This command
will configure a provided file but with a few specific differences from the
standard :command:`configure_file` way.
To properly utilize this function, the input file should have a single line
with the text ``@PACKAGE_INIT@`` in addition to the content that is desired.
That variable will be replaced with a block of code which turns set values into
relative paths. These values which are new can be referenced by the same name
but prepended with a ``PACKAGE_`` prefix.
.. literalinclude:: Step12/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-configure-package-config.cmake
:language: cmake
:start-after: # install the configuration targets
:end-before: # generate the version file
The :command:`write_basic_package_version_file` is next. This command writes
a file which is used by :command:`find_package`, documenting the version and
compatibility of the desired package. Here, we use the ``Tutorial_VERSION_*``
variables and say that it is compatible with ``AnyNewerVersion``, which
denotes that this version or any higher one are compatible with the requested
version.
.. literalinclude:: Step12/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-basic-version-file.cmake
:language: cmake
:start-after: # generate the version file
:end-before: # install the generated configuration files
Finally, set both generated files to be installed:
.. literalinclude:: Step12/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-install-configured-files.cmake
:language: cmake
:start-after: # install the generated configuration files
:end-before: # generate the export
At this point, we have generated a relocatable CMake Configuration for our
project that can be used after the project has been installed or packaged. If
we want our project to also be used from a build directory we only have to add
the following to the bottom of the top level ``CMakeLists.txt``:
.. literalinclude:: Step12/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-export
:language: cmake
:start-after: # needs to be after the install(TARGETS) command
With this export call we now generate a ``MathFunctionsTargets.cmake``, allowing the
configured ``MathFunctionsConfig.cmake`` in the build directory to be used by
other projects, without needing it to be installed.

View File

@@ -1,168 +0,0 @@
Step 4: Adding Generator Expressions
=====================================
:manual:`Generator expressions <cmake-generator-expressions(7)>` are evaluated
during build system generation to produce information specific to each build
configuration.
:manual:`Generator expressions <cmake-generator-expressions(7)>` are allowed in
the context of many target properties, such as :prop_tgt:`LINK_LIBRARIES`,
:prop_tgt:`INCLUDE_DIRECTORIES`, :prop_tgt:`COMPILE_DEFINITIONS` and others.
They may also be used when using commands to populate those properties, such as
:command:`target_link_libraries`, :command:`target_include_directories`,
:command:`target_compile_definitions` and others.
:manual:`Generator expressions <cmake-generator-expressions(7)>` may be used
to enable conditional linking, conditional definitions used when compiling,
conditional include directories and more. The conditions may be based on the
build configuration, target properties, platform information or any other
queryable information.
There are different types of
:manual:`generator expressions <cmake-generator-expressions(7)>` including
Logical, Informational, and Output expressions.
Logical expressions are used to create conditional output. The basic
expressions are the ``0`` and ``1`` expressions. A ``$<0:...>`` results in the
empty string, and ``$<1:...>`` results in the content of ``...``. They can also
be nested.
Exercise 1 - Adding Compiler Warning Flags with Generator Expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A common usage of
:manual:`generator expressions <cmake-generator-expressions(7)>` is to
conditionally add compiler flags, such as those for language levels or
warnings. A nice pattern is to associate this information to an ``INTERFACE``
target allowing this information to propagate.
Goal
----
Add compiler warning flags when building but not for installed versions.
Helpful Resources
-----------------
* :manual:`cmake-generator-expressions(7)`
* :command:`cmake_minimum_required`
* :command:`set`
* :command:`target_compile_options`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
Open the file ``Step4/CMakeLists.txt`` and complete ``TODO 1`` through
``TODO 4``.
First, in the top level ``CMakeLists.txt`` file, we need to set the
:command:`cmake_minimum_required` to ``3.15``. In this exercise we are going
to use a generator expression which was introduced in CMake 3.15.
Next we add the desired compiler warning flags that we want for our project.
As warning flags vary based on the compiler, we use the
``COMPILE_LANG_AND_ID`` generator expression to control which flags to apply
given a language and a set of compiler ids.
Build and Run
-------------
Make a new directory called ``Step4_build``, run the :manual:`cmake <cmake(1)>`
executable or the :manual:`cmake-gui <cmake-gui(1)>` to configure the project
and then build it with your chosen build tool or by using ``cmake --build .``
from the build directory.
.. code-block:: console
mkdir Step4_build
cd Step4_build
cmake ../Step4
cmake --build .
Solution
--------
Update the :command:`cmake_minimum_required` to require at least CMake
version ``3.15``:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step5/CMakeLists.txt
:caption: TODO 1: CMakeLists.txt
:name: MathFunctions-CMakeLists.txt-minimum-required-step4
:language: cmake
:end-before: # set the project name and version
.. raw:: html
</details>
Next we determine which compiler our system is currently using to build
since warning flags vary based on the compiler we use. This is done with
the ``COMPILE_LANG_AND_ID`` generator expression. We set the result in the
variables ``gcc_like_cxx`` and ``msvc_cxx`` as follows:
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step5/CMakeLists.txt
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-compile_lang_and_id
:language: cmake
:start-after: # the BUILD_INTERFACE genex
:end-before: target_compile_options(tutorial_compiler_flags INTERFACE
.. raw:: html
</details>
Next we add the desired compiler warning flags that we want for our project.
Using our variables ``gcc_like_cxx`` and ``msvc_cxx``, we can use another
generator expression to apply the respective flags only when the variables are
true. We use :command:`target_compile_options` to apply these flags to our
interface library.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-compile_flags
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
"$<${msvc_cxx}:-W3>"
)
.. raw:: html
</details>
Lastly, we only want these warning flags to be used during builds. Consumers
of our installed project should not inherit our warning flags. To specify
this, we wrap our flags from TODO 3 in a generator expression using the
``BUILD_INTERFACE`` condition. The resulting full code looks like the following:
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. literalinclude:: Step5/CMakeLists.txt
:caption: TODO 4: CMakeLists.txt
:name: CMakeLists.txt-target_compile_options-genex
:language: cmake
:start-after: set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
:end-before: # configure a header file to pass some of the CMake settings
.. raw:: html
</details>

View File

@@ -1,108 +0,0 @@
Step 6: Adding Support for a Testing Dashboard
==============================================
Adding support for submitting our test results to a dashboard is simple. We
already defined a number of tests for our project in
:ref:`Testing Support <Tutorial Testing Support>`. Now we just have to run
those tests and submit them to CDash.
Exercise 1 - Send Results to a Testing Dashboard
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Goal
----
Display our CTest results with CDash.
Helpful Resources
-----------------
* :manual:`ctest(1)`
* :command:`include`
* :module:`CTest`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
For this exercise, complete ``TODO 1`` in the top-level ``CMakeLists.txt`` by
including the :module:`CTest` module. This will enable testing with CTest as
well as dashboard submissions to CDash, so we can safely remove the call to
:command:`enable_testing`.
We will also need to acquire a ``CTestConfig.cmake`` file to be placed in the
top-level directory. When run, the :manual:`ctest <ctest(1)>` executable will
read this file to gather information about the testing dashboard. It contains:
* The project "Nightly" start time
* The time when a 24 hour "day" starts for this project.
* The URL of the CDash instance where the submission's generated documents
will be sent
For this tutorial, a public dashboard server is used and its corresponding
``CTestConfig.cmake`` file is provided for you in this step's root directory.
In practice, this file would be downloaded from a project's ``Settings`` page
on the CDash instance intended to host the test results. Once downloaded from
CDash, the file should not be modified locally.
.. literalinclude:: Step7/CTestConfig.cmake
:caption: CTestConfig.cmake
:name: CTestConfig.cmake
:language: cmake
Build and Run
-------------
Note that as part of the CDash submission some information about your
development system (e.g. site name or full pathnames) may displayed publicly.
To create a simple test dashboard, run the :manual:`cmake <cmake(1)>`
executable or the :manual:`cmake-gui <cmake-gui(1)>` to configure the project
but do not build it yet. Instead, navigate to the build directory and run:
.. code-block:: console
ctest [-VV] -D Experimental
Remember, for multi-config generators (e.g. Visual Studio), the configuration
type must be specified:
.. code-block:: console
ctest [-VV] -C Debug -D Experimental
Or, from an IDE, build the ``Experimental`` target.
The :manual:`ctest <ctest(1)>` executable will build the project, run any
tests, and submit the results to Kitware's public dashboard:
https://my.cdash.org/index.php?project=CMakeTutorial.
Solution
--------
The only CMake code changed needed in this step was to enable dashboard
submissions to CDash by including the :module:`CTest` module in our top-level
``CMakeLists.txt``:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step7/CMakeLists.txt
:caption: TODO 1: CMakeLists.txt
:name: CMakeLists.txt-include-CTest
:language: cmake
:start-after: # enable testing
:end-before: # does the application run
.. raw:: html
</details>

View File

@@ -1,163 +0,0 @@
Step 7: Adding System Introspection
===================================
Let us consider adding some code to our project that depends on features the
target platform may not have. For this example, we will add some code that
depends on whether or not the target platform has the ``log`` and ``exp``
functions. Of course almost every platform has these functions but for this
tutorial assume that they are not common.
Exercise 1 - Assessing Dependency Availability
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Goal
----
Change implementation based on available system dependencies.
Helpful Resources
-----------------
* :module:`CheckCXXSourceCompiles`
* :command:`target_compile_definitions`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/mysqrt.cxx``
Getting Started
---------------
The starting source code is provided in the ``Step7`` directory. In this
exercise, complete ``TODO 1`` through ``TODO 5``.
Start by editing ``MathFunctions/CMakeLists.txt``. Include the
:module:`CheckCXXSourceCompiles` module. Then, use
``check_cxx_source_compiles()`` to determine whether ``log`` and ``exp`` are
available from ``cmath``. If they are available, use
:command:`target_compile_definitions` to specify ``HAVE_LOG`` and ``HAVE_EXP``
as compile definitions.
In the ``MathFunctions/mysqrt.cxx``, include ``cmath``. Then, if the system has
``log`` and ``exp``, use them to compute the square root.
Build and Run
-------------
Make a new directory called ``Step7_build``. Run the
:manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool and run the ``Tutorial`` executable.
This can look like the following:
.. code-block:: console
mkdir Step7_build
cd Step7_build
cmake ../Step7
cmake --build .
Which function gives better results now, ``sqrt`` or ``mysqrt``?
Solution
--------
In this exercise we will use functions from the
:module:`CheckCXXSourceCompiles` module so first we must include it in
``MathFunctions/CMakeLists.txt``.
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/CMakeLists.txt
:caption: TODO 1: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-include-check_cxx_source_compiles
:language: cmake
:start-after: # does this system provide the log and exp functions?
:end-before: check_cxx_source_compiles
.. raw:: html
</details>
Then test for the availability of
``log`` and ``exp`` using ``check_cxx_compiles_source``. This function
lets us try compiling simple code with the required dependency prior to
the true source code compilation. The resulting variables ``HAVE_LOG``
and ``HAVE_EXP`` represent whether those dependencies are available.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/CMakeLists.txt
:caption: TODO 2: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-check_cxx_source_compiles
:language: cmake
:start-after: include(CheckCXXSourceCompiles)
:end-before: # add compile definitions
.. raw:: html
</details>
Next, we need to pass these CMake variables to our source code. This way,
our source code can tell what resources are available. If both ``log`` and
``exp`` are available, use :command:`target_compile_definitions` to specify
``HAVE_LOG`` and ``HAVE_EXP`` as ``PRIVATE`` compile definitions.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/CMakeLists.txt
:caption: TODO 3: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-target_compile_definitions
:language: cmake
:start-after: # add compile definitions
:end-before: # state
.. raw:: html
</details>
Since we may be using ``log`` and ``exp``, we need to modify
``mysqrt.cxx`` to include ``cmath``.
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/mysqrt.cxx
:caption: TODO 4: MathFunctions/mysqrt.cxx
:name: MathFunctions/mysqrt.cxx-include-cmath
:language: c++
:start-after: #include "mysqrt.h"
:end-before: include <iostream>
.. raw:: html
</details>
If ``log`` and ``exp`` are available on the system, then use them to
compute the square root in the ``mysqrt`` function. The ``mysqrt`` function in
``MathFunctions/mysqrt.cxx`` will look as follows:
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/mysqrt.cxx
:caption: TODO 5: MathFunctions/mysqrt.cxx
:name: MathFunctions/mysqrt.cxx-ifdef
:language: c++
:start-after: // if we have both log and exp then use them
:end-before: return result;
.. raw:: html
</details>

View File

@@ -1,304 +0,0 @@
Step 3: Adding Usage Requirements for a Library
===============================================
Exercise 1 - Adding Usage Requirements for a Library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:ref:`Usage requirements <Target Usage Requirements>` of a target parameters
allow for far better control over a library or executable's link and include
line while also giving more control over the transitive property of targets
inside CMake. The primary commands that
leverage usage requirements are:
* :command:`target_compile_definitions`
* :command:`target_compile_options`
* :command:`target_include_directories`
* :command:`target_link_directories`
* :command:`target_link_options`
* :command:`target_precompile_headers`
* :command:`target_sources`
Goal
----
Add usage requirements for a library.
Helpful Materials
-----------------
* :variable:`CMAKE_CURRENT_SOURCE_DIR`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``CMakeLists.txt``
Getting Started
---------------
In this exercise, we will refactor our code from
:guide:`tutorial/Adding a Library` to use the modern CMake approach. We will
let our library define its own usage requirements so they are passed
transitively to other targets as necessary. In this case, ``MathFunctions``
will specify any needed include directories itself. Then, the consuming target
``Tutorial`` simply needs to link to ``MathFunctions`` and not worry about
any additional include directories.
The starting source code is provided in the ``Step3`` directory. In this
exercise, complete ``TODO 1`` through ``TODO 3``.
First, add a call to :command:`target_include_directories` in
``MathFunctions/CMakeLists``. Remember that
:variable:`CMAKE_CURRENT_SOURCE_DIR` is the path to the source directory
currently being processed.
Then, update (and simplify!) the call to
:command:`target_include_directories` in the top-level ``CMakeLists.txt``.
Build and Run
-------------
Make a new directory called ``Step3_build``, run the :manual:`cmake
<cmake(1)>` executable or the :manual:`cmake-gui <cmake-gui(1)>` to
configure the project and then build it with your chosen build tool or by
using :option:`cmake --build . <cmake --build>` from the build directory.
Here's a refresher of what that looks like from the command line:
.. code-block:: console
mkdir Step3_build
cd Step3_build
cmake ../Step3
cmake --build .
Next, use the newly built ``Tutorial`` and verify that it is working as
expected.
Solution
--------
Let's update the code from the previous step to use the modern CMake
approach of usage requirements.
We want to state that anybody linking to ``MathFunctions`` needs to include
the current source directory, while ``MathFunctions`` itself doesn't. This
can be expressed with an ``INTERFACE`` usage requirement. Remember
``INTERFACE`` means things that consumers require but the producer doesn't.
At the end of ``MathFunctions/CMakeLists.txt``, use
:command:`target_include_directories` with the ``INTERFACE`` keyword, as
follows:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step4/MathFunctions/CMakeLists.txt
:caption: TODO 1: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-target_include_directories-INTERFACE
:language: cmake
:start-after: # to find MathFunctions.h
:end-before: # should we use our own
.. raw:: html
</details>
Now that we've specified usage requirements for ``MathFunctions`` we can
safely remove our uses of the ``EXTRA_INCLUDES`` variable from the top-level
``CMakeLists.txt``.
Remove this line:
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step3/CMakeLists.txt
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-remove-EXTRA_INCLUDES
:language: cmake
:start-after: add_subdirectory(MathFunctions)
:end-before: # add the executable
.. raw:: html
</details>
And remove ``EXTRA_INCLUDES`` from ``target_include_directories``:
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. literalinclude:: Step4/CMakeLists.txt
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-target_include_directories-remove-EXTRA_INCLUDES
:language: cmake
:start-after: # so that we will find TutorialConfig.h
.. raw:: html
</details>
Notice that with this technique, the only thing our executable target does to
use our library is call :command:`target_link_libraries` with the name
of the library target. In larger projects, the classic method of specifying
library dependencies manually becomes very complicated very quickly.
Exercise 2 - Setting the C++ Standard with Interface Libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now that we have switched our code to a more modern approach, let's demonstrate
a modern technique to set properties to multiple targets.
Let's refactor our existing code to use an ``INTERFACE`` library. We will
use that library in the next step to demonstrate a common use for
:manual:`generator expressions <cmake-generator-expressions(7)>`.
Goal
----
Add an ``INTERFACE`` library target to specify the required C++ standard.
Helpful Resources
-----------------
* :command:`add_library`
* :command:`target_compile_features`
* :command:`target_link_libraries`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``MathFunctions/CMakeLists.txt``
Getting Started
---------------
In this exercise, we will refactor our code to use an ``INTERFACE`` library to
specify the C++ standard.
Start this exercise from what we left at the end of Step3 exercise 1. You will
have to complete ``TODO 4`` through ``TODO 7``.
Start by editing the top level ``CMakeLists.txt`` file. Construct an
``INTERFACE`` library target called ``tutorial_compiler_flags`` and
specify ``cxx_std_11`` as a target compiler feature.
Modify ``CMakeLists.txt`` and ``MathFunctions/CMakeLists.txt`` so that all
targets have a :command:`target_link_libraries` call to
``tutorial_compiler_flags``.
Build and Run
-------------
Since we have our build directory already configured from Exercise 1, simply
rebuild our code by calling the following:
.. code-block:: console
cd Step3_build
cmake --build .
Next, use the newly built ``Tutorial`` and verify that it is working as
expected.
Solution
--------
Let's update our code from the previous step to use interface libraries
to set our C++ requirements.
To start, we need to remove the two :command:`set` calls on the variables
:variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`.
The specific lines to remove are as follows:
.. literalinclude:: Step3/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-CXX_STANDARD-variable-remove
:language: cmake
:start-after: # specify the C++ standard
:end-before: # configure a header file
Next, we need to create an interface library, ``tutorial_compiler_flags``. And
then use :command:`target_compile_features` to add the compiler feature
``cxx_std_11``.
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. literalinclude:: Step4/CMakeLists.txt
:caption: TODO 4: CMakeLists.txt
:name: CMakeLists.txt-cxx_std-feature
:language: cmake
:start-after: # specify the C++ standard
:end-before: # TODO 2: Create helper
.. raw:: html
</details>
Finally, with our interface library set up, we need to link our
executable ``Tutorial``, our ``SqrtLibrary`` library and our ``MathFunctions``
library to our new ``tutorial_compiler_flags`` library. Respectively, the code
will look like this:
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step4/CMakeLists.txt
:caption: TODO 5: CMakeLists.txt
:name: CMakeLists.txt-target_link_libraries-step4
:language: cmake
:start-after: add_executable(Tutorial tutorial.cxx)
:end-before: # add the binary tree to the search path for include file
.. raw:: html
</details>
this:
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. literalinclude:: Step4/MathFunctions/CMakeLists.txt
:caption: TODO 6: MathFunctions/CMakeLists.txt
:name: MathFunctions-CMakeLists.txt-target_link_libraries-step4
:language: cmake
:start-after: # link SqrtLibrary to tutorial_compiler_flags
:end-before: target_link_libraries(MathFunctions
.. raw:: html
</details>
and this:
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step4/MathFunctions/CMakeLists.txt
:caption: TODO 7: MathFunctions/CMakeLists.txt
:name: MathFunctions-SqrtLibrary-target_link_libraries-step4
:language: cmake
:start-after: # link MathFunctions to tutorial_compiler_flags
.. raw:: html
</details>
With this, all of our code still requires C++ 11 to build. Notice
though that with this method, it gives us the ability to be specific about
which targets get specific requirements. In addition, we create a single
source of truth in our interface library.

View File

@@ -1,103 +0,0 @@
Step 8: Adding a Custom Command and Generated File
==================================================
Suppose, for the purpose of this tutorial, we decide that we never want to use
the platform ``log`` and ``exp`` functions and instead would like to
generate a table of precomputed values to use in the ``mysqrt`` function.
In this section, we will create the table as part of the build process,
and then compile that table into our application.
First, let's remove the check for the ``log`` and ``exp`` functions in
``MathFunctions/CMakeLists.txt``. Then remove the check for ``HAVE_LOG`` and
``HAVE_EXP`` from ``mysqrt.cxx``. At the same time, we can remove
:code:`#include <cmath>`.
In the ``MathFunctions`` subdirectory, a new source file named
``MakeTable.cxx`` has been provided to generate the table.
After reviewing the file, we can see that the table is produced as valid C++
code and that the output filename is passed in as an argument.
The next step is to create ``MathFunctions/MakeTable.cmake``. Then, add the
appropriate commands to the file to build the ``MakeTable`` executable and
then run it as part of the build process. A few commands are needed to
accomplish this.
First, we add an executable for ``MakeTable``.
.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
:caption: MathFunctions/MakeTable.cmake
:name: MathFunctions/MakeTable.cmake-add_executable-MakeTable
:language: cmake
:start-after: # first we add the executable that generates the table
:end-before: target_link_libraries
After creating the executable, we add the ``tutorial_compiler_flags`` to our
executable using :command:`target_link_libraries`.
.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
:caption: MathFunctions/MakeTable.cmake
:name: MathFunctions/MakeTable.cmake-link-tutorial-compiler-flags
:language: cmake
:start-after: add_executable
:end-before: # add the command to generate
Then we add a custom command that specifies how to produce ``Table.h``
by running MakeTable.
.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
:caption: MathFunctions/MakeTable.cmake
:name: MathFunctions/MakeTable.cmake-add_custom_command-Table.h
:language: cmake
:start-after: # add the command to generate the source code
Next we have to let CMake know that ``mysqrt.cxx`` depends on the generated
file ``Table.h``. This is done by adding the generated ``Table.h`` to the list
of sources for the library ``SqrtLibrary``.
.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add_library-Table.h
:language: cmake
:start-after: # library that just does sqrt
:end-before: # state that we depend on
We also have to add the current binary directory to the list of include
directories so that ``Table.h`` can be found and included by ``mysqrt.cxx``.
.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-target_include_directories-Table.h
:language: cmake
:start-after: # state that we depend on our bin
:end-before: target_link_libraries
As the last step, we need to include
``MakeTable.cmake`` at the top of the ``MathFunctions/CMakeLists.txt``.
.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-include-MakeTable.cmake
:language: cmake
:start-after: # generate Table.h
:end-before: # library that just does sqrt
Now let's use the generated table. First, modify ``mysqrt.cxx`` to include
``Table.h``. Next, we can rewrite the ``mysqrt`` function to use the table:
.. literalinclude:: Step9/MathFunctions/mysqrt.cxx
:caption: MathFunctions/mysqrt.cxx
:name: MathFunctions/mysqrt.cxx
:language: c++
:start-after: // a hack square root calculation using simple operations
Run the :manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool.
When this project is built it will first build the ``MakeTable`` executable.
It will then run ``MakeTable`` to produce ``Table.h``. Finally, it will
compile ``mysqrt.cxx`` which includes ``Table.h`` to produce the
``MathFunctions`` library.
Run the Tutorial executable and verify that it is using the table.

View File

@@ -1,455 +0,0 @@
Step 2: Adding a Library
========================
At this point, we have seen how to create a basic project using CMake. In this
step, we will learn how to create and use a library in our project. We will
also see how to make the use of our library optional.
Exercise 1 - Creating a Library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To add a library in CMake, use the :command:`add_library` command and specify
which source files should make up the library.
Rather than placing all of the source files in one directory, we can organize
our project with one or more subdirectories. In this case, we will create a
subdirectory specifically for our library. Here, we can add a new
``CMakeLists.txt`` file and one or more source files. In the top level
``CMakeLists.txt`` file, we will use the :command:`add_subdirectory` command
to add the subdirectory to the build.
Once the library is created, it is connected to our executable target with
:command:`target_include_directories` and :command:`target_link_libraries`.
Goal
----
Add and use a library.
Helpful Resources
-----------------
* :command:`add_library`
* :command:`add_subdirectory`
* :command:`target_include_directories`
* :command:`target_link_libraries`
* :variable:`PROJECT_SOURCE_DIR`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``tutorial.cxx``
* ``MathFunctions/CMakeLists.txt``
Getting Started
---------------
In this exercise, we will add a library to our project that contains our own
implementation for computing the square root of a number. The executable can
then use this library instead of the standard square root function provided by
the compiler.
For this tutorial we will put the library into a subdirectory called
``MathFunctions``. This directory already contains the header files
``MathFunctions.h`` and ``mysqrt.h``. Their respective source files
``MathFunctions.cxx`` and ``mysqrt.cxx`` are also provided. We will not need
to modify any of these files. ``mysqrt.cxx`` has one function called
``mysqrt`` that provides similar functionality to the compiler's ``sqrt``
function. ``MathFunctions.cxx`` contains one function ``sqrt`` which serves
to hide the implementation details of ``sqrt``.
From the ``Help/guide/tutorial/Step2`` directory, start with ``TODO 1`` and
complete through ``TODO 6``.
First, fill in the one line ``CMakeLists.txt`` in the ``MathFunctions``
subdirectory.
Next, edit the top level ``CMakeLists.txt``.
Finally, use the newly created ``MathFunctions`` library in ``tutorial.cxx``
Build and Run
-------------
Run the :manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool.
Below is a refresher of what that looks like from the command line:
.. code-block:: console
mkdir Step2_build
cd Step2_build
cmake ../Step2
cmake --build .
Try to use the newly built ``Tutorial`` and ensure that it is still
producing accurate square root values.
Solution
--------
In the ``CMakeLists.txt`` file in the ``MathFunctions`` directory, we create
a library target called ``MathFunctions`` with :command:`add_library`. The
source files for the library are passed as an argument to
:command:`add_library`. This looks like the following line:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 1: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add_library
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
.. raw:: html
</details>
To make use of the new library we will add an :command:`add_subdirectory`
call in the top-level ``CMakeLists.txt`` file so that the library will get
built.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-add_subdirectory
add_subdirectory(MathFunctions)
.. raw:: html
</details>
Next, the new library target is linked to the executable target using
:command:`target_link_libraries`.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-target_link_libraries
target_link_libraries(Tutorial PUBLIC MathFunctions)
.. raw:: html
</details>
Finally we need to specify the library's header file location.
Modify the existing :command:`target_include_directories` call
to add the ``MathFunctions`` subdirectory as an include directory
so that the ``MathFunctions.h`` header file can be found.
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 4: CMakeLists.txt
:name: CMakeLists.txt-target_include_directories-step2
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
.. raw:: html
</details>
Now let's use our library. In ``tutorial.cxx``, include ``MathFunctions.h``:
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step3/tutorial.cxx
:caption: TODO 5: tutorial.cxx
:name: CMakeLists.txt-include-MathFunctions.h
:language: cmake
:start-after: #include <string>
:end-before: #include "TutorialConfig.h"
.. raw:: html
</details>
Lastly, replace ``sqrt`` with the wrapper function ``mathfunctions::sqrt``.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. literalinclude:: Step3/tutorial.cxx
:caption: TODO 6: tutorial.cxx
:name: CMakeLists.txt-option
:language: cmake
:start-after: double const inputValue = std::stod(argv[1]);
:end-before: std::cout
.. raw:: html
</details>
Exercise 2 - Adding an Option
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now let us add an option in the MathFunctions library to allow developers to
select either the custom square root implementation or the built in standard
implementation. While for the tutorial
there really isn't any need to do so, for larger projects this is a common
occurrence.
CMake can do this using the :command:`option` command. This gives users a
variable which they can change when configuring their cmake build. This
setting will be stored in the cache so that the user does not need to set
the value each time they run CMake on a build directory.
Goal
----
Add the option to build without ``MathFunctions``.
Helpful Resources
-----------------
* :command:`if`
* :command:`option`
* :command:`target_compile_definitions`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/MathFunctions.cxx``
Getting Started
---------------
Start with the resulting files from Exercise 1. Complete ``TODO 7`` through
``TODO 14``.
First create a variable ``USE_MYMATH`` using the :command:`option` command
in ``MathFunctions/CMakeLists.txt``. In that same file, use that option
to pass a compile definition to the ``MathFunctions`` library.
Then, update ``MathFunctions.cxx`` to redirect compilation based on
``USE_MYMATH``.
Lastly, prevent ``mysqrt.cxx`` from being compiled when ``USE_MYMATH`` is on
by making it its own library inside of the ``USE_MYMATH`` block of
``MathFunctions/CMakeLists.txt``.
Build and Run
-------------
Since we have our build directory already configured from Exercise 1, we can
rebuild by simply calling the following:
.. code-block:: console
cd ../Step2_build
cmake --build .
Next, run the ``Tutorial`` executable on a few numbers to verify that it's
still correct.
Now let's update the value of ``USE_MYMATH`` to ``OFF``. The easiest way is to
use the :manual:`cmake-gui <cmake-gui(1)>` or :manual:`ccmake <ccmake(1)>`
if you're in the terminal. Or, alternatively, if you want to change the
option from the command-line, try:
.. code-block:: console
cmake ../Step2 -DUSE_MYMATH=OFF
Now, rebuild the code with the following:
.. code-block:: console
cmake --build .
Then, run the executable again to ensure that it still works with
``USE_MYMATH`` set to ``OFF``. Which function gives better results, ``sqrt``
or ``mysqrt``?
Solution
--------
The first step is to add an option to ``MathFunctions/CMakeLists.txt``.
This option will be displayed in the :manual:`cmake-gui <cmake-gui(1)>` and
:manual:`ccmake <ccmake(1)>` with a default value of ``ON`` that can be
changed by the user.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 7: MathFunctions/CMakeLists.txt
:name: CMakeLists.txt-option-library-level
:language: cmake
:start-after: # should we use our own math functions
:end-before: if (USE_MYMATH)
.. raw:: html
</details>
Next, make building and linking our library with ``mysqrt`` function
conditional using this new option.
Create an :command:`if` statement which checks the value of
``USE_MYMATH``. Inside the :command:`if` block, put the
:command:`target_compile_definitions` command with the compile
definition ``USE_MYMATH``.
.. raw:: html
<details><summary>TODO 8: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 8: MathFunctions/CMakeLists.txt
:name: CMakeLists.txt-USE_MYMATH
if (USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()
.. raw:: html
</details>
When ``USE_MYMATH`` is ``ON``, the compile definition ``USE_MYMATH`` will
be set. We can then use this compile definition to enable or disable
sections of our source code.
The corresponding changes to the source code are fairly straightforward.
In ``MathFunctions.cxx``, we make ``USE_MYMATH`` control which square root
function is used:
.. raw:: html
<details><summary>TODO 9: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/MathFunctions.cxx
:caption: TODO 9: MathFunctions/MathFunctions.cxx
:name: MathFunctions-USE_MYMATH-if
:language: c++
:start-after: which square root function should we use?
:end-before: }
.. raw:: html
</details>
Next, we need to include ``mysqrt.h`` if ``USE_MYMATH`` is defined.
.. raw:: html
<details><summary>TODO 10: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/MathFunctions.cxx
:caption: TODO 10: MathFunctions/MathFunctions.cxx
:name: MathFunctions-USE_MYMATH-if-include
:language: c++
:start-after: include <cmath>
:end-before: namespace mathfunctions
.. raw:: html
</details>
Finally, we need to include ``cmath`` now that we are using ``std::sqrt``.
.. raw:: html
<details><summary>TODO 11: Click to show/hide answer</summary>
.. code-block:: c++
:caption: TODO 11 : MathFunctions/MathFunctions.cxx
:name: tutorial.cxx-include_cmath
#include <cmath>
.. raw:: html
</details>
At this point, if ``USE_MYMATH`` is ``OFF``, ``mysqrt.cxx`` would not be used
but it will still be compiled because the ``MathFunctions`` target has
``mysqrt.cxx`` listed under sources.
There are a few ways to fix this. The first option is to use
:command:`target_sources` to add ``mysqrt.cxx`` from within the ``USE_MYMATH``
block. Another option is to create an additional library within the
``USE_MYMATH`` block which is responsible for compiling ``mysqrt.cxx``. For
the sake of this tutorial, we are going to create an additional library.
First, from within ``USE_MYMATH`` create a library called ``SqrtLibrary``
that has sources ``mysqrt.cxx``.
.. raw:: html
<details><summary>TODO 12: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 12 : MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add_library-SqrtLibrary
:language: cmake
:start-after: # library that just does sqrt
:end-before: # TODO 7: Link
.. raw:: html
</details>
Next, we link ``SqrtLibrary`` onto ``MathFunctions`` when ``USE_MYMATH`` is
enabled.
.. raw:: html
<details><summary>TODO 13: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 13 : MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-target_link_libraries-SqrtLibrary
:language: cmake
:start-after: to tutorial_compiler_flags
:end-before: endif()
.. raw:: html
</details>
Finally, we can remove ``mysqrt.cxx`` from our ``MathFunctions`` library
source list because it will be pulled in when ``SqrtLibrary`` is included.
.. raw:: html
<details><summary>TODO 14: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 14 : MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-remove-mysqrt.cxx-MathFunctions
:language: cmake
:end-before: # TODO 1:
.. raw:: html
</details>
With these changes, the ``mysqrt`` function is now completely optional to
whoever is building and using the ``MathFunctions`` library. Users can toggle
``USE_MYMATH`` to manipulate what library is used in the build.

View File

@@ -0,0 +1,583 @@
Step 2: CMake Language Fundamentals
===================================
In the previous step we rushed through and handwaved several aspects of the
CMake language which is used within ``CMakeLists.txt`` in order to get useful,
building programs as soon as possible. However, in the wild we encounter
a great deal more complexity than simply describing lists of source and
header files.
To deal with this complexity CMake provides a Turing-complete domain-specific
language for describing the process of building software. Understanding the
fundamentals of this language will be necessary as we write more complex
CMLs and other CMake files. The language is formally known as
":manual:`CMake Language <cmake-language(7)>`", or more colloquially as CMakeLang.
.. note::
The CMake Language is not well suited to describing things which are not
related to building software. While it has some features for general purpose
use, developers should use caution when solving problems not directly related
to their build in CMake Language.
Oftentimes the correct answer is to write a tool in a general purpose
programming language which solves the problem, and teach CMake how to invoke
that tool as part of the build process. Code generation, cryptographic
signature utilities, and even ray-tracers have been written in CMake Language,
but this is not a recommended practice.
Because we want to fully explore the language features, this step is an
exception to the tutorial sequencing. It neither builds on ``Step1``, nor is the
starting point for ``Step3``. This will be a sandbox to explore language
features without building any software. We'll pick back up with the Tutorial
program in ``Step3``.
.. note::
This tutorial endeavors to demonstrate best practices and solutions to real
problems. However, for this one step we're going to be re-implementing some
built-in CMake functions. In "real life", do not write your own
:command:`list(APPEND)`.
Background
^^^^^^^^^^
The only fundamental types in CMakeLang are strings and lists. Every object in
CMake is a string, and lists are themselves strings which contain semicolons
as separators. Any command which appears to operate on something other than a
string, whether they be booleans, numbers, JSON objects, or otherwise, is in
fact consuming a string, doing some internal conversion logic (in a language
other than CMakeLang), and then converting back to a string for any potential
output.
We can create a variable, which is to say a name for a string, using the
:command:`set` command.
.. code-block:: cmake
set(var "World!")
A variable's value can be accessed using brace expansion, for example if we want
to use the :command:`message` command to print the string named by ``var``.
.. code-block:: cmake
set(var "World!")
message("Hello ${var}")
.. code-block:: console
$ cmake -P CMakeLists.txt
Hello World!
.. note::
:option:`cmake -P` is called "script mode", it informs CMake this file is not
intended to have a :command:`project` command. We're not building any
software, instead using CMake only as a command interpreter.
Because CMakeLang has only strings, conditionals are entirely by convention of
which strings are considered true and which are considered false. These are
*supposed* to be intuitive, "True", "On", "Yes", and (strings representing)
non-zero numbers are truthy, while "False" "Off", "No", "0", "Ignore",
"NotFound", and the empty string are all considered false.
However, some of the rules are more complex than that, so taking some time
to consult the :command:`if` documentation on expressions is worthwhile. It's
recommended to stick to a single pair for a given context, such as
"True"/"False" or "On"/"Off".
As mentioned, lists are strings containing semicolons. The :command:`list`
command is useful for manipulating these, and many structures within CMake
expect to operate with this convention. As an example, we can use the
:command:`foreach` command to iterate over a list.
.. code-block:: cmake
set(stooges "Moe;Larry")
list(APPEND stooges "Curly")
message("Stooges contains: ${stooges}")
foreach(stooge IN LISTS stooges)
message("Hello, ${stooge}")
endforeach()
.. code-block:: console
$ cmake -P CMakeLists.txt
Stooges contains: Moe;Larry;Curly
Hello, Moe
Hello, Larry
Hello, Curly
Exercise 1 - Macros, Functions, and Lists
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CMake allows us to craft our own functions and macros. This can be very helpful
when constructing lots of similar targets, like tests, for which we will want
to call similar sets of commands over and over again. We do so with
:command:`function` and :command:`macro`.
.. code-block:: cmake
macro(MyMacro MacroArgument)
message("${MacroArgument}\n\t\tFrom Macro")
endmacro()
function(MyFunc FuncArgument)
MyMacro("${FuncArgument}\n\tFrom Function")
endfunction()
MyFunc("From TopLevel")
.. code-block:: console
$ cmake -P CMakeLists.txt
From TopLevel
From Function
From Macro
Like with many languages, the difference between functions and macros is one
of scope. In CMakeLang, both :command:`function` and :command:`macro` can "see"
all the variables created in all the frames above them. However, a
:command:`macro` acts semantically like a text replacement, similar to C/C++
macros, so any side effects the macro creates are visible in their calling
context. If we create or change a variable in a macro, the caller will see the
change.
:command:`function` creates its own variable scope, so side effects are not
visible to the caller. In order to propagate changes to the parent which called
the function, we must use ``set(<var> <value> PARENT_SCOPE)``, which works the
same as :command:`set` but for variables belonging to the caller's context.
.. note::
In CMake 3.25, the :command:`return(PROPAGATE)` option was added, which
works the same as :command:`set(PARENT_SCOPE)` but provides slightly better
ergonomics.
While not necessary for this exercise, it bears mentioning that :command:`macro`
and :command:`function` both support variadic arguments via the ``ARGV``
variable, a list containing all arguments passed to the command, and the
``ARGN`` variable, containing all arguments past the last expected argument.
We're not going to build any targets in this exercise, so instead we'll
construct our own version of :command:`list(APPEND)`, which adds a value to a
list.
Goal
----
Implement a macro and a function which append a value to a list, without using
the :command:`list(APPEND)` command.
The desired usage of these commands is as follows:
.. code-block:: cmake
set(Letters "Alpha;Beta")
MacroAppend(Letters "Gamma")
message("Letters contains: ${Letters}")
.. code-block:: console
$ cmake -P Exercise1.cmake
Letters contains: Alpha;Beta;Gamma
.. note::
The extension for these exercises is ``.cmake``, that's the standard extension
for CMakeLang files when not contained in a ``CMakeLists.txt``
Helpful Resources
-----------------
* :command:`macro`
* :command:`function`
* :command:`set`
* :command:`if`
Files to Edit
-------------
* ``Exercise1.cmake``
Getting Started
----------------
The source code for ``Exercise1.cmake`` is provided in the
``Help/guide/tutorial/Step2`` directory. It contains tests to verify the
append behavior described above.
.. note::
You're not expected to handle the case of an empty or undefined list to
append to. However, as a bonus, the case is tested if you want to try out
your understanding of CMakeLang conditionals.
Complete ``TODO 1`` and ``TODO 2``.
Build and Run
-------------
We're going to use script mode to run these exercises. First navigate to the
``Help/guide/tutorial/Step2`` folder then you can run the code with:
.. code-block:: console
cmake -P Exercise1.cmake
The script will report if the commands were implemented correctly.
Solution
--------
This problem relies on an understanding of the mechanisms of CMake variables.
CMake variables are names for strings; or put another way, a CMake variable
is itself a string which can brace expand into a different string.
This leads to a common pattern in CMake code where functions and macros aren't
passed values, but rather, they are passed the names of variables which contain
those values. Thus ``ListVar`` does not contain the *value* of the list we need
to append to, it contains the *name* of a list, which contains the value we
need to append to.
When expanding the variable with ``${ListVar}``, we will get the name of the
list. If we expand that name with ``${${ListVar}}``, we will get the values
the list contains.
To implement ``MacroAppend``, we need only combine this understanding of
``ListVar`` with our knowledge of the :command:`set` command.
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 1: Exercise1.cmake
:name: Exercise1.cmake-MacroAppend
macro(MacroAppend ListVar Value)
set(${ListVar} "${${ListVar}};${Value}")
endmacro()
.. raw:: html
</details>
We don't need to worry about scope here, because a macro operates in the same
scope as its parent.
``FuncAppend`` is almost identical, in fact it could be implemented in the
same one liner but with an added ``PARENT_SCOPE``, but the instructions ask
us to implement it in terms of ``MacroAppend``.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 2: Exercise1.cmake
:name: Exercise1.cmake-FuncAppend
function(FuncAppend ListVar Value)
MacroAppend(${ListVar} ${Value})
set(${ListVar} "${${ListVar}}" PARENT_SCOPE)
endfunction()
.. raw:: html
</details>
``MacroAppend`` transforms ``ListVar`` for us, but it won't propagate the result
to the parent scope. Because this is a function, we need to do so ourselves
with :command:`set(PARENT_SCOPE)`.
Exercise 2 - Conditionals and Loops
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The two most common flow control elements in any structured programming
language are conditionals and their close sibling loops. CMakeLang is no
different. As previously mentioned, the truthiness of a given CMake string is a
convention established by the :command:`if` command.
When given a string, :command:`if` will first check if it is one of the known
constant values previously discussed. If the string isn't one of those values
the command assumes it is a variable, and checks the brace-expanded contents of
that variable to determine the result of the conditional.
.. code-block:: cmake
if(True)
message("Constant Value: True")
else()
message("Constant Value: False")
endif()
if(ConditionalValue)
message("Undefined Variable: True")
else()
message("Undefined Variable: False")
endif()
set(ConditionalValue True)
if(ConditionalValue)
message("Defined Variable: True")
else()
message("Defined Variable: False")
endif()
.. code-block:: console
$ cmake -P ConditionalValue.cmake
Constant Value: True
Undefined Variable: False
Defined Variable: True
.. note::
This is a good a time as any to discuss quoting in CMake. All objects in
CMake are strings, thus the double quote, ``"``, is often unnecessary.
CMake knows the object is a string, everything is a string.
However, it is needed in some contexts. Strings containing whitespace require
double quotes, else they are treated like lists; CMake will concatenate the
elements together with semicolons. The reverse is also true, when
brace-expanding lists it is necessary to do so inside quotes if we want to
*preserve* the semicolons. Otherwise CMake will expand the list items into
space-separate strings.
A handful of commands, such as :command:`if`, recognize the difference
between quoted and unquoted strings. :command:`if` will only check that the
given string represents a variable when the string is unquoted.
Finally, :command:`if` provides several useful comparison modes such as
``STREQUAL`` for string matching, ``DEFINED`` for checking the existence of
a variable, and ``MATCHES`` for regular expression checks. It also supports the
typical logical operators, ``NOT``, ``AND``, and ``OR``.
In addition to conditionals CMake provides two loop structures,
:command:`while`, which follows the same rules as :command:`if` for checking a
loop variable, and the more useful :command:`foreach`, which iterates over lists
of strings and was demonstrated in the `Background`_ section.
For this exercise, we're going to use loops and conditionals to solve some
simple problems. We'll be using the aforementioned ``ARGN`` variable from
:command:`function` as the list to operate on.
Goal
----
Loop over a list, and return all the strings containing the string ``Foo``.
.. note::
Those who read the command documentation will be aware that this is
:command:`list(FILTER)`, resist the temptation to use it.
Helpful Resources
-----------------
* :command:`function`
* :command:`foreach`
* :command:`if`
* :command:`list`
Files to Edit
-------------
* ``Exercise2.cmake``
Getting Started
----------------
The source code for ``Exercise2.cmake`` is provided in the ``Help/guide/tutorial/Step2``
directory. It contains tests to verify the append behavior described above.
.. note::
You should use the :command:`list(APPEND)` command this time to collect your
final result into a list. The input can be consumed from the ``ARGN`` variable
of the provided function.
Complete ``TODO 3``.
Build and Run
-------------
Navigate to the ``Help/guide/tutorial/Step2`` folder then you can run the code with:
.. code-block:: console
cmake -P Exercise2.cmake
The script will report if the ``FilterFoo`` function was implemented correctly.
Solution
--------
We need to do three things, loop over the ``ARGN`` list, check if a given
item in that list matches ``"Foo"``, and if so append it to the ``OutVar``
list.
While there are a couple ways we could invoke :command:`foreach`, the
recommended way is to allow the command to do the variable expansion for us
via ``IN LISTS`` to access the ``ARGN`` list items.
The :command:`if` comparison we need is ``MATCHES`` which will check if
``"FOO"`` exists in the item. All that remains is to append the item to the
``OutVar`` list. The trickiest part is remembering that ``OutVar`` *names* a
list, it is not the list itself, so we need to access it via ``${OutVar}``.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 3: Exercise2.cmake
:name: Exercise2.cmake-FilterFoo
function(FilterFoo OutVar)
foreach(item IN LISTS ARGN)
if(item MATCHES Foo)
list(APPEND ${OutVar} ${item})
endif()
endforeach()
set(${OutVar} ${${OutVar}} PARENT_SCOPE)
endfunction()
.. raw:: html
</details>
Exercise 3 - Organizing with Include
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We have already discussed how to incorporate subdirectories containing their
own CMLs with :command:`add_subdirectory`. In later steps we will explore
the various way CMake code can be packaged and shared across projects.
However for small CMake functions and utilities, it is often beneficial for them
to live in their own ``.cmake`` files outside the project CMLs and separate
from the rest of the build system. This allows for separation of concerns,
removing the project-specific elements from the utilities we are using to
describe them.
To incorporate these separate ``.cmake`` files into our project, we use the
:command:`include` command. This command immediately begins interpreting the
contents of the :command:`include`'d file in the scope of the parent CML. It
is as if the entire file were being called as a macro.
Traditionally, these kinds of ``.cmake`` files live in a folder named "cmake"
inside the project root. For this exercise, we'll use the ``Step2`` folder instead.
Goal
----
Use the functions from Exercises 1 and 2 to build and filter our own list of items.
Helpful Resources
-----------------
* :command:`include`
Files to Edit
-------------
* ``Exercise3.cmake``
Getting Started
----------------
The source code for ``Exercise3.cmake`` is provided in the ``Help/guide/tutorial/Step2``
directory. It contains tests to verify the correct usage of our functions
from the previous two exercises.
.. note::
Actually it reuses tests from Exercise2.cmake, reusable code is good for
everyone.
Complete ``TODO 4`` through ``TODO 7``.
Build and Run
-------------
Navigate to the ``Help/guide/tutorial/Step2`` folder then you can run the code with:
.. code-block:: console
cmake -P Exercise3.cmake
The script will report if the functions were invoked and composed correctly.
Solution
--------
The :command:`include` command will interpret the included file completely,
including the tests from the first two exercises. We don't want to run these
tests again. Thanks to some forethought, these files check a variable called
``SKIP_TESTS`` prior to running their tests, setting this to ``True`` will
get us the behavior we want.
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 4: Exercise3.cmake
:name: Exercise3.cmake-SKIP_TESTS
set(SKIP_TESTS True)
.. raw:: html
</details>
Now we're ready to :command:`include` the previous exercises to grab their
functions.
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 5: Exercise3.cmake
:name: Exercise3.cmake-include
include(Exercise1.cmake)
include(Exercise2.cmake)
.. raw:: html
</details>
Now that ``FuncAppend`` is available to us, we can use it to append new elements
to the ``InList``.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 6: Exercise3.cmake
:name: Exercise3.cmake-FuncAppend
FuncAppend(InList FooBaz)
FuncAppend(InList QuxBaz)
.. raw:: html
</details>
Finally, we can use ``FilterFoo`` to filter the full list. The tricky part to
remember here is that our ``FilterFoo`` wants to operate on list values via
``ARGN``, so we need to expand the ``InList`` when we call ``FilterFoo``.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 7: Exercise3.cmake
:name: Exercise3.cmake-FilterFoo
FilterFoo(OutList ${InList})
.. raw:: html
</details>

View File

@@ -1,127 +0,0 @@
cmake_minimum_required(VERSION 3.15)
# set the project name and version
project(Tutorial VERSION 1.0)
set(CMAKE_DEBUG_POSTFIX d)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# add compiler warning flags just when building this project via
# the BUILD_INTERFACE genex
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
if(APPLE)
set(CMAKE_INSTALL_RPATH "@executable_path/../lib")
elseif(UNIX)
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
endif()
# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
# add the install targets
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
# enable testing
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction()
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
# setup installer
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_GENERATOR "TGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")
include(CPack)
# install the configuration targets
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/example"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# generate the version file for the config file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
# install the configuration file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
DESTINATION lib/cmake/MathFunctions
)
# generate the export targets for the build tree
# needs to be after the install(TARGETS) command
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

View File

@@ -1,3 +0,0 @@
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
set(CTEST_SUBMIT_URL "https://my.cdash.org/submit.php?project=CMakeTutorial")

View File

@@ -1,4 +0,0 @@
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

View File

@@ -1,2 +0,0 @@
This is the open source License.txt file introduced in
CMake/Tutorial/Step9...

View File

@@ -1,62 +0,0 @@
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
include(MakeTable.cmake) # generates Table.h
# library that just does sqrt
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# state that we depend on our binary dir to find Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
# state that SqrtLibrary need PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
# link SqrtLibrary to tutorial_compiler_flags
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# link MathFunctions to tutorial_compiler_flags
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
# setup the version numbering
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
# install libs
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs}
EXPORT MathFunctionsTargets
DESTINATION lib)
# install include headers
install(FILES MathFunctions.h DESTINATION include)

View File

@@ -1,10 +0,0 @@
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
# add the command to generate the source code
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)

View File

@@ -1,20 +0,0 @@
#include "MathFunctions.h"
#include <cmath>
#ifdef USE_MYMATH
# include "mysqrt.h"
#endif
namespace mathfunctions {
double sqrt(double x)
{
// which square root function should we use?
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}

View File

@@ -1,14 +0,0 @@
#if defined(_WIN32)
# if defined(EXPORTING_MYMATH)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double x);
}

View File

@@ -1,37 +0,0 @@
#include <iostream>
#include "MathFunctions.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}

View File

@@ -1,6 +0,0 @@
namespace mathfunctions {
namespace detail {
double mysqrt(double x);
}
}

View File

@@ -1,6 +0,0 @@
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)

View File

@@ -0,0 +1,53 @@
# A very simple test framework for demonstrating how dependencies work
cmake_minimum_required(VERSION 3.23)
project(SimpleTest
VERSION 0.0.1
)
add_library(SimpleTest INTERFACE)
target_sources(SimpleTest
INTERFACE
FILE_SET HEADERS
FILES
SimpleTest.h
)
target_compile_features(SimpleTest INTERFACE cxx_std_20)
target_compile_definitions(SimpleTest INTERFACE "SIMPLETEST_CONFIG=$<CONFIG>")
find_package(TransitiveDep REQUIRED)
target_link_libraries(SimpleTest
INTERFACE
TransitiveDep::TransitiveDep
)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
install(
TARGETS SimpleTest
EXPORT SimpleTestTargets
FILE_SET HEADERS
)
install(
EXPORT SimpleTestTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SimpleTest
NAMESPACE SimpleTest::
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/SimpleTestConfigVersion.cmake
COMPATIBILITY ExactVersion
ARCH_INDEPENDENT
)
install(
FILES
cmake/simpletest_discover_impl.cmake
cmake/simpletest_discover_tests.cmake
cmake/SimpleTestConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/SimpleTestConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SimpleTest
)

View File

@@ -0,0 +1,16 @@
{
"version": 4,
"configurePresets": [
{
"name": "tutorial",
"displayName": "SimpleTest Preset",
"description": "Preset to use with the tutorial's SimpleTest library",
"binaryDir": "${sourceDir}/build",
"installDir": "${sourceParentDir}/install",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "20",
"CMAKE_PREFIX_PATH": "${sourceParentDir}/install"
}
}
]
}

View File

@@ -0,0 +1,155 @@
#pragma once
#include <cstdio>
#include <map>
#include <string_view>
namespace SimpleTest {
using TestFunc = void (*)();
using Registry = std::map<std::string_view, TestFunc, std::less<>>;
inline Registry g_registry;
inline Registry& registry()
{
return g_registry;
}
struct failure
{
char const* file;
int line;
char const* expr;
};
struct Registrar
{
template <std::size_t N>
Registrar(char const (&name)[N], TestFunc f)
{
auto [it, inserted] =
registry().emplace(std::string_view{ name, N ? (N - 1) : 0 }, f);
if (!inserted) {
std::printf("[ WARN ] duplicate test name: %.*s\n",
int(it->first.size()), it->first.data());
}
}
};
inline Registry const& all()
{
return registry();
}
inline TestFunc find(std::string_view name)
{
auto it = registry().find(name);
return it == registry().end() ? nullptr : it->second;
}
}
#define SIMPLETEST_STRINGIFY(a) #a
#define SIMPLETEST_XSTRINGIFY(a) SIMPLETEST_STRINGIFY(a)
#define SIMPLETEST_CONCAT_(a, b) a##b
#define SIMPLETEST_CONCAT(a, b) SIMPLETEST_CONCAT_(a, b)
#define TEST(name_literal) \
static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)(); \
static ::SimpleTest::Registrar SIMPLETEST_CONCAT(_simpletest_reg_, \
__LINE__)( \
name_literal, &SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)); \
static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)()
// Minimal assertion
#define REQUIRE(expr) \
do { \
if (!(expr)) \
throw ::SimpleTest::failure{ __FILE__, __LINE__, #expr }; \
} while (0)
int main(int argc, char** argv)
{
using namespace ::SimpleTest;
std::string_view arg1 =
(argc >= 2) ? std::string_view{ argv[1] } : std::string_view{};
if (arg1 == "--list") {
bool first = true;
for (auto const& [name, _] : registry()) {
if (!first)
std::printf(",");
std::printf("%.*s", int(name.size()), name.data());
first = false;
}
std::printf("\n");
return 0;
}
if (arg1 == "--test") {
if (argc < 3) {
std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
return 2;
}
#ifdef SIMPLETEST_CONFIG
std::printf("SimpleTest built with config: " SIMPLETEST_XSTRINGIFY(
SIMPLETEST_CONFIG) "\n");
#endif
std::string_view name{ argv[2] };
auto it = registry().find(name);
if (it == registry().end()) {
std::printf("[ NOTFOUND ] %s\n", argv[2]);
return 2;
}
int failed = 0;
std::printf("[ RUN ] %.*s\n", int(it->first.size()),
it->first.data());
try {
it->second();
std::printf("[ OK] %.*s\n", int(it->first.size()),
it->first.data());
} catch (failure const& f) {
std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(it->first.size()),
it->first.data(), f.file, f.line, f.expr);
failed = 1;
} catch (...) {
std::printf("[ FAILED ] %.*s : unknown exception\n",
int(it->first.size()), it->first.data());
failed = 1;
}
return failed;
}
if (argc > 1) {
std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
return 2;
}
#ifdef SIMPLETEST_CONFIG
std::printf("SimpleTest built with config: " SIMPLETEST_XSTRINGIFY(
SIMPLETEST_CONFIG) "\n");
#endif
// Default: run all tests.
int failed = 0;
for (auto const& [name, func] : all()) {
std::printf("[ RUN ] %.*s\n", int(name.size()), name.data());
try {
func();
std::printf("[ OK ] %.*s\n", int(name.size()), name.data());
} catch (failure const& f) {
std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(name.size()),
name.data(), f.file, f.line, f.expr);
failed = 1;
} catch (...) {
std::printf("[ FAILED ] %.*s : unknown exception\n", int(name.size()),
name.data());
failed = 1;
}
}
return failed;
}

View File

@@ -0,0 +1,5 @@
include(CMakeFindDependencyMacro)
find_dependency(TransitiveDep)
include(${CMAKE_CURRENT_LIST_DIR}/SimpleTestTargets.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/simpletest_discover_tests.cmake)

View File

@@ -0,0 +1,32 @@
if(NOT DEFINED TEST_EXE OR NOT DEFINED OUT_FILE)
# noqa: spellcheck off
message(FATAL_ERROR "simpletest_discover: need -DTEST_EXE and -DOUT_FILE")
# noqa: spellcheck on
endif()
execute_process(
COMMAND ${TEST_EXE} --list
RESULT_VARIABLE _rc
OUTPUT_VARIABLE _out
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _rc EQUAL 0)
file(WRITE ${OUT_FILE} "# simpletest: --list failed (rc=${_rc})\n")
message(FATAL_ERROR "simpletest_discover: '${TEST_EXE} --list' failed (${_rc})\n${_err}")
endif()
if(_out STREQUAL "")
file(WRITE ${OUT_FILE} "# simpletest: no tests\n")
return()
endif()
string(REPLACE "," ";" _names "${_out}")
file(WRITE ${OUT_FILE} "# Auto-generated by simpletest_discover_impl.cmake\n")
foreach(_name IN LISTS _names)
file(APPEND ${OUT_FILE}
"add_test([=[${_name}]=] \"${TEST_EXE}\" \"--test\" \"${_name}\")\n"
)
endforeach()

View File

@@ -0,0 +1,27 @@
set(_simpletest_impl_script ${CMAKE_CURRENT_LIST_DIR}/simpletest_discover_impl.cmake)
function(simpletest_discover_tests target)
if(NOT TARGET ${target})
message(FATAL_ERROR "simpletest_discover_tests: no such target '${target}'")
endif()
set(_out ${CMAKE_CURRENT_BINARY_DIR}/${target}_ctests.cmake)
if(NOT EXISTS ${_out})
file(WRITE ${_out} "# Populated after building ${target}\n")
endif()
# noqa: spellcheck off
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DTEST_EXE=$<TARGET_FILE:${target}>
-DOUT_FILE=${_out}
-P ${_simpletest_impl_script}
BYPRODUCTS ${_out}
COMMENT "SimpleTest: Discovering tests in ${target}"
VERBATIM
)
# noqa: spellcheck on
set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES ${_out})
endfunction()

View File

@@ -1,3 +0,0 @@
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

View File

@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.23)
project(Tutorial
VERSION 1.0.0
)
option(TUTORIAL_BUILD_UTILITIES "Build the Tutorial executable" ON)
option(TUTORIAL_USE_STD_SQRT "Use std::sqrt" OFF)
option(TUTORIAL_ENABLE_IPO "Check for and use IPO support" ON)
option(BUILD_TESTING "Enable testing and build tests" ON)
if(TUTORIAL_ENABLE_IPO)
include(CheckIPOSupported)
check_ipo_supported(RESULT result OUTPUT output)
if(result)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
else()
message(WARNING "IPO is not supported ${message}")
endif()
endif()
if(TUTORIAL_BUILD_UTILITIES)
add_subdirectory(Tutorial)
endif()
if(BUILD_TESTING)
enable_testing()
add_subdirectory(Tests)
endif()
add_subdirectory(MathFunctions)
include(GNUInstallDirs)
install(
TARGETS MathFunctions OpAdd OpMul OpSub MathLogger SqrtTable
EXPORT TutorialTargets
FILE_SET HEADERS
)
install(
EXPORT TutorialTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
NAMESPACE Tutorial::
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
COMPATIBILITY ExactVersion
)
install(
FILES
cmake/TutorialConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)

View File

@@ -0,0 +1,16 @@
{
"version": 4,
"configurePresets": [
{
"name": "tutorial",
"displayName": "Tutorial Preset",
"description": "Preset to use with the tutorial",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "${sourceParentDir}/install",
"TUTORIAL_USE_STD_SQRT": "OFF",
"TUTORIAL_ENABLE_IPO": "OFF"
}
}
]
}

View File

@@ -0,0 +1,55 @@
add_library(MathFunctions)
add_library(Tutorial::MathFunctions ALIAS MathFunctions)
target_sources(MathFunctions
PRIVATE
MathFunctions.cxx
PUBLIC
FILE_SET HEADERS
FILES
MathFunctions.h
)
target_link_libraries(MathFunctions
PRIVATE
MathLogger
SqrtTable
PUBLIC
OpAdd
OpMul
OpSub
)
target_compile_features(MathFunctions PRIVATE cxx_std_20)
if(TUTORIAL_USE_STD_SQRT)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_STD_SQRT)
endif()
include(CheckIncludeFiles)
check_include_files(emmintrin.h HAS_EMMINTRIN LANGUAGE CXX)
if(HAS_EMMINTRIN)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_SSE2)
endif()
include(CheckSourceCompiles)
check_source_compiles(CXX
[=[
typedef double v2df __attribute__((vector_size(16)));
int main() {
__builtin_ia32_sqrtsd(v2df{});
}
]=]
HAS_GNU_BUILTIN
)
if(HAS_GNU_BUILTIN)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_GNU_BUILTIN)
endif()
add_subdirectory(MathLogger)
add_subdirectory(MathExtensions)
add_subdirectory(MakeTable)

View File

@@ -0,0 +1,28 @@
add_executable(MakeTable)
target_sources(MakeTable
PRIVATE
MakeTable.cxx
)
add_custom_command(
OUTPUT SqrtTable.h
COMMAND MakeTable SqrtTable.h
DEPENDS MakeTable
VERBATIM
)
add_custom_target(RunMakeTable DEPENDS SqrtTable.h)
add_library(SqrtTable INTERFACE)
target_sources(SqrtTable
INTERFACE
FILE_SET HEADERS
BASE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
FILES
${CMAKE_CURRENT_BINARY_DIR}/SqrtTable.h
)
add_dependencies(SqrtTable RunMakeTable)

View File

@@ -0,0 +1,3 @@
add_subdirectory(OpAdd)
add_subdirectory(OpMul)
add_subdirectory(OpSub)

View File

@@ -0,0 +1,11 @@
add_library(OpAdd OBJECT)
target_sources(OpAdd
PRIVATE
OpAdd.cxx
INTERFACE
FILE_SET HEADERS
FILES
OpAdd.h
)

View File

@@ -0,0 +1,6 @@
namespace mathfunctions {
double OpAdd(double a, double b)
{
return a + b;
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace mathfunctions {
double OpAdd(double a, double b);
}

View File

@@ -0,0 +1,11 @@
add_library(OpMul OBJECT)
target_sources(OpMul
PRIVATE
OpMul.cxx
INTERFACE
FILE_SET HEADERS
FILES
OpMul.h
)

View File

@@ -0,0 +1,6 @@
namespace mathfunctions {
double OpMul(double a, double b)
{
return a * b;
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace mathfunctions {
double OpMul(double a, double b);
}

View File

@@ -0,0 +1,11 @@
add_library(OpSub OBJECT)
target_sources(OpSub
PRIVATE
OpSub.cxx
INTERFACE
FILE_SET HEADERS
FILES
OpSub.h
)

View File

@@ -0,0 +1,6 @@
namespace mathfunctions {
double OpSub(double a, double b)
{
return a - b;
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace mathfunctions {
double OpSub(double a, double b);
}

View File

@@ -0,0 +1,101 @@
#include <cmath>
#include <format>
#include <MathLogger.h>
#ifdef TUTORIAL_USE_SSE2
# include <emmintrin.h>
#endif
namespace {
mathlogger::Logger Logger;
#if defined(TUTORIAL_USE_GNU_BUILTIN)
typedef double v2df __attribute__((vector_size(16)));
double gnu_mysqrt(double x)
{
v2df root = __builtin_ia32_sqrtsd(v2df{ x, 0.0 });
double result = root[0];
Logger.Log(std::format("Computed sqrt of {} to be {} with GNU-builtins\n", x,
result));
return result;
}
#elif defined(TUTORIAL_USE_SSE2)
double sse2_mysqrt(double x)
{
__m128d root = _mm_sqrt_sd(_mm_setzero_pd(), _mm_set_sd(x));
double result = _mm_cvtsd_f64(root);
Logger.Log(
std::format("Computed sqrt of {} to be {} with SSE2\n", x, result));
return result;
}
#endif
// a hack square root calculation using simple operations
double fallback_mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result = x;
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
Logger.Log(std::format("Computing sqrt of {} to be {}\n", x, result));
}
return result;
}
#include <SqrtTable.h>
double table_sqrt(double x)
{
double result = sqrtTable[static_cast<int>(x)];
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
}
Logger.Log(
std::format("Computed sqrt of {} to be {} with TableSqrt\n", x, result));
return result;
}
double mysqrt(double x)
{
if (x >= 1 && x < 10) {
return table_sqrt(x);
}
#if defined(TUTORIAL_USE_GNU_BUILTIN)
return gnu_mysqrt(x);
#elif defined(TUTORIAL_USE_SSE2)
return sse2_mysqrt(x);
#else
return fallback_mysqrt(x);
#endif
}
}
namespace mathfunctions {
double sqrt(double x)
{
#ifdef TUTORIAL_USE_STD_SQRT
return std::sqrt(x);
#else
return mysqrt(x);
#endif
}
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <OpAdd.h>
#include <OpMul.h>
#include <OpSub.h>
namespace mathfunctions {
double sqrt(double x);
}

View File

@@ -0,0 +1,6 @@
add_library(MathLogger INTERFACE)
target_sources(MathLogger
INTERFACE
FILE_SET HEADERS
)

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
namespace mathlogger {
enum LogLevel
{
INFO,
WARN,
ERROR,
};
inline std::string FormatLog(LogLevel level, std::string const& message)
{
switch (level) {
case INFO:
return "INFO: " + message;
case WARN:
return "WARN: " + message;
case ERROR:
return "ERROR: " + message;
}
return "UNKNOWN: " + message;
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <string>
#include "MathFormatting.h"
#include "MathOutput.h"
namespace mathlogger {
struct Logger
{
LogLevel level = INFO;
void SetLevel(LogLevel new_level) { level = new_level; }
void Log(std::string const& message)
{
std::string formatted = FormatLog(level, message);
WriteLog(formatted);
}
};
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <iostream>
#include <string>
namespace mathlogger {
inline void WriteLog(std::string const& msg)
{
std::cout << msg;
}
}

View File

@@ -0,0 +1,16 @@
add_executable(TestMathFunctions)
target_sources(TestMathFunctions
PRIVATE
TestMathFunctions.cxx
)
find_package(SimpleTest REQUIRED)
target_link_libraries(TestMathFunctions
PRIVATE
MathFunctions
SimpleTest::SimpleTest
)
simpletest_discover_tests(TestMathFunctions)

View File

@@ -0,0 +1,22 @@
#include <MathFunctions.h>
#include <SimpleTest.h>
TEST("add")
{
REQUIRE(mathfunctions::OpAdd(2.0, 2.0) == 4.0);
}
TEST("sub")
{
REQUIRE(mathfunctions::OpSub(4.0, 2.0) == 2.0);
}
TEST("mul")
{
REQUIRE(mathfunctions::OpMul(5.0, 5.0) == 25.0);
}
TEST("sqrt")
{
REQUIRE(mathfunctions::sqrt(25.0) == 5.0);
}

View File

@@ -0,0 +1,39 @@
add_executable(Tutorial)
target_sources(Tutorial
PRIVATE
Tutorial.cxx
)
target_link_libraries(Tutorial
PRIVATE
MathFunctions
)
target_compile_features(Tutorial PRIVATE cxx_std_20)
if(
(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR
(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
)
target_compile_options(Tutorial PRIVATE /W3)
elseif(
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
)
target_compile_options(Tutorial PRIVATE -Wall)
endif()
find_path(UnpackagedIncludeFolder Unpackaged.h REQUIRED
PATH_SUFFIXES
Unpackaged
)
target_include_directories(Tutorial
PRIVATE
${UnpackagedIncludeFolder}
)

View File

@@ -0,0 +1,27 @@
// A simple program that computes the square root of a number
#include <format>
#include <iostream>
#include <string>
#include <MathFunctions.h>
#include <Unpackaged.h>
int main(int argc, char* argv[])
{
if (argc < 2) {
std::cout << std::format("Usage: {} number\n", argv[0]);
return 1;
}
// convert input to double
double const inputValue = std::stod(argv[1]);
// calculate square root
double const outputValue = mathfunctions::sqrt(inputValue);
std::cout << std::format("The square root of {} is {}\n", inputValue,
outputValue);
double const checkValue = mathfunctions::OpMul(outputValue, outputValue);
std::cout << std::format("The square of {} is {}\n", outputValue,
checkValue);
}

View File

@@ -0,0 +1 @@
include(${CMAKE_CURRENT_LIST_DIR}/TutorialTargets.cmake)

View File

@@ -0,0 +1,3 @@
#pragma once
#define UNPACKAGED_HEADER_FOUND

View File

@@ -0,0 +1,50 @@
# Abridged import written for the Tutorial
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
message(FATAL_ERROR "CMake >= 3.0.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "3.0.0")
message(FATAL_ERROR "CMake >= 3.0.0 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 3.0.0...3.30)
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS TransitiveDep::TransitiveDep)
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
if(TARGET "${_cmake_expected_target}")
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
else()
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
# Create imported target TransitiveDep::TransitiveDep
add_library(TransitiveDep::TransitiveDep INTERFACE IMPORTED)
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)

View File

@@ -0,0 +1,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:: cmake
//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>

View File

@@ -0,0 +1,279 @@
Step 7: Custom Commands and Generated Files
===========================================
Code generation is a ubiquitous mechanism for extending programming languages
beyond the bounds of their language model. CMake has first-class support for
Qt's Meta-Object Compiler, but very few other code generators are notable
enough to warrant that kind of effort.
Instead, code generators tend to be bespoke and usage specific. CMake provides
facilities for describing the usage of a code generator, so projects can
add support for their individual needs.
In this step, we will use :command:`add_custom_command` to add support for a
code generator within the tutorial project.
Background
^^^^^^^^^^
Any step in the build process can generally be described in terms of its inputs
and outputs. CMake assumes that code generators and other custom processes
operate on the same principle. In this way, the code generator acts identically
to compilers, linkers, and other elements of the toolchain; when the inputs are
newer than the outputs (or the outputs don't exist), a user-specified command
will be run to update the outputs.
.. note::
This model assumes the outputs of a process are known before it is run. CMake
lacks the ability to describe code generators where the name and location of
the outputs depends on the *content* of the input. Various hacks exist to
shim this functionality into CMake, but they are outside the scope of this
tutorial.
Describing a code generator (or any custom process) is usually performed in
two parts. First, the inputs and outputs are described independently of the
CMake target model, concerned only with the generation process itself. Second,
the outputs are associated with a CMake target to insert them into the CMake
target model.
For sources, this is as simple as adding the generated files to the source list
of a ``STATIC``, ``SHARED``, or ``OBJECT`` library. For header-only generators,
it's often necessary to use an intermediary target created via
:command:`add_custom_target` to add the header file generation to the
build stage (because ``INTERFACE`` libraries have no build step).
Exercise 1 - Using a Code Generator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The primary mechanism for describing a code generator is the
:command:`add_custom_command` command. A "command", for the purpose of
:command:`add_custom_command` is either an executable available in the build
environment or a CMake executable target name.
.. code-block:: cmake
add_executable(Tool)
# ...
add_custom_command(
OUTPUT Generated.cxx
COMMAND Tool -i input.txt -o Generated.cxx
DEPENDS Tool input.txt
VERBATIM
)
# ...
add_library(GeneratedObject OBJECT)
target_sources(GeneratedObject
PRIVATE
Generated.cxx
)
Most of the keywords are self-explanatory, with the exception of ``VERBATIM``.
This argument is effectively mandatory for legacy reasons that are uninteresting
to explain in a modern context. The curious should consult the
:command:`add_custom_command` documentation for additional details.
The ``Tool`` executable target appears both in the ``COMMAND`` and ``DEPENDS``
parameters. While ``COMMAND`` is sufficient for the code to build correctly,
adding the ``Tool`` itself as a dependency of the custom command ensure that
if ``Tool`` is updated, the custom command will be rerun.
For header-only file generation, additional commands are necessary because the
library itself has no build step. We can use :command:`add_custom_target` to
create an "artificial" build step for the library. We then force the custom
target to be run before any targets which link the library with the command
:command:`add_dependencies`.
.. code-block:: cmake
add_custom_target(RunGenerator DEPENDS Generated.h)
add_library(GeneratedLib INTERFACE)
target_sources(GeneratedLib
INTERFACE
FILE_SET HEADERS
BASE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
FILES
${CMAKE_CURRENT_BINARY_DIR}/Generated.h
)
add_dependencies(GeneratedLib RunGenerator)
.. note::
We add the :variable:`CMAKE_CURRENT_BINARY_DIR`, a variable which names the
current location in the build tree where our artifacts are being placed, to
the base directories because that's the working directory our code generator
will be run inside of. Listing the ``FILES`` is unnecessary for the build and
done so here only for clarity.
Goal
----
Add a generated table of pre-computed square roots to the ``MathFunctions``
library.
Helpful Resources
-----------------
* :command:`add_executable`
* :command:`add_library`
* :command:`target_sources`
* :command:`add_custom_command`
* :command:`add_custom_target`
* :command:`add_dependencies`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/MakeTable/CMakeLists.txt``
* ``MathFunctions/MathFunctions.cxx``
Getting Started
---------------
The ``MathFunctions`` library has been edited to use a pre-computed table when
given a number less than 10. However, the hardcoded table is not particularly
accurate, containing only the nearest truncated integer value.
The ``MakeTable.cxx`` source file describes a program which will generate a
better table. It takes a single argument as input, the file name of the table
to be generated.
Complete ``TODO 1`` through ``TODO 10``.
Build and Run
-------------
No special configuration is needed, configure and build as usual. Note that
the ``MakeTable`` executable is sequenced before ``MathFunctions``.
.. code-block:: console
cmake --preset tutorial
cmake --build build
Verify the output of ``Tutorial`` now uses the pre-computed table for values
less than 10.
Solution
--------
First we add a new executable to generate the tables, adding the
``MakeTable.cxx`` file as a source.
.. raw:: html
<details><summary>TODO 1-2: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
:caption: TODO 1-2: MathFunctions/MakeTable/CMakeLists.txt
:name: MathFunctions/MakeTable/CMakeLists.txt-add_executable
:language: cmake
:start-at: add_executable
:end-at: MakeTable.cxx
:append: )
.. raw:: html
</details>
Then we add a custom command which produces the table, and custom target which
depends on the table.
.. raw:: html
<details><summary>TODO 3-4: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
:caption: TODO 3-4: MathFunctions/MakeTable/CMakeLists.txt
:name: MathFunctions/MakeTable/CMakeLists.txt-add_custom_command
:language: cmake
:start-at: add_custom_command
:end-at: add_custom_target
.. raw:: html
</details>
We need to add an interface library which describes the output which will
appear in :variable:`CMAKE_CURRENT_BINARY_DIR`. The ``FILES`` parameter is
optional.
.. raw:: html
<details><summary>TODO 5-6: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
:caption: TODO 5-6: MathFunctions/MakeTable/CMakeLists.txt
:name: MathFunctions/MakeTable/CMakeLists.txt-add_library
:language: cmake
:start-at: add_library
:end-at: SqrtTable.h
:append: )
.. raw:: html
</details>
Now that all the targets are described, we can force the custom target to run
before any dependents of the interface library by associating them with
:command:`add_dependencies`.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
:caption: TODO 7: MathFunctions/MakeTable/CMakeLists.txt
:name: MathFunctions/MakeTable/CMakeLists.txt-add_dependencies
:language: cmake
:start-at: add_dependencies
:end-at: add_dependencies
.. raw:: html
</details>
We are ready to add the interface library to the linked libraries of
``MathFunctions``, and add the entire ``MakeTable`` folder to the project.
.. raw:: html
<details><summary>TODO 8-9: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/CMakeLists.txt
:caption: TODO 8: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-link-sqrttable
:language: cmake
:start-at: target_link_libraries(MathFunctions
:end-at: )
.. literalinclude:: Step8/MathFunctions/CMakeLists.txt
:caption: TODO 9: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add-maketable
:language: cmake
:start-at: add_subdirectory(MakeTable
:end-at: add_subdirectory(MakeTable
.. raw:: html
</details>
Finally, we update the ``MathFunctions`` library itself to take advantage of
the generated table.
.. raw:: html
<details><summary>TODO 10: Click to show/hide answer</summary>
.. literalinclude:: Step8/MathFunctions/MathFunctions.cxx
:caption: TODO 10: MathFunctions/MathFunctions.cxx
:name: MathFunctions/MathFunctions.cxx-include-sqrttable
:language: c++
:start-at: #include <SqrtTable.h>
:end-at: {
.. raw:: html
</details>

View File

@@ -0,0 +1,529 @@
Step 10: Finding Dependencies
=============================
In C/C++ software development, managing build dependencies is consistently
one of the highest ranked challenges facing modern developers. CMake provides
an extensive toolset for discovering and validating dependencies of different
kinds.
However, for correctly packaged projects there is no need to use these advanced
tools. Many popular library and utility projects today produce correct install
trees, like the one we set up in ``Step 9``, which are easy is to integrate
into CMake.
In this best-case scenario, we only need the :command:`find_package` to
import dependencies into our project.
Background
^^^^^^^^^^
There are five principle commands used for discovering dependencies with
CMake, the first four are:
:command:`find_file`
Finds and reports the full path to a named file, this tends to be the
most flexible of the ``find`` commands.
:command:`find_library`
Finds and reports the full path to a static archive or shared object
suitable for use with :command:`target_link_libraries`.
:command:`find_path`
Finds and reports the full path to a directory *containing* a file. This
is most commonly used for headers in combination with
:command:`target_include_directories`.
:command:`find_program`
Finds and reports and invocable name or path for a program. Often used in
combination with :command:`execute_process` or :command:`add_custom_command`.
These commands should be considered "backup", used when the primary find command
is unsuitable. The primary find command is :command:`find_package`. It uses
comprehensive built-in heuristics and upstream-provided packaging files to
provide the best interface to the requested dependency.
Exercise 1 - Using ``find_package()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The search paths and behaviors used by :command:`find_package` are fully
described in its documentation, but much too verbose to replicate here. Suffice
to say it searches well known, lesser known, obscure, and user-provided
locations attempting to find a package which meets the requirements given to it.
.. code-block:: cmake
find_package(ForeignLibrary)
The best way to use :command:`find_package` is to ensure all dependencies have
been installed to a single install tree prior to the build, and then make the
location of that install tree known to :command:`find_package` via the
:variable:`CMAKE_PREFIX_PATH` variable.
.. note::
Building and installing dependencies can itself be an immense amount of labor.
While this tutorial will do so for illustration purposes, it is **extremely**
recommended that a package manager be used for project-local dependency
management.
:command:`find_package` accepts several parameters besides the package to be
found. The most notable are:
* A positional ``<version>`` argument, for describing a version to be checked
against the package's config version file. This should be used sparingly,
it is better to control the version of the dependency being installed via
a package manager than possibly break the build on otherwise innocuous
version updates.
If the package is known to rely on an older version of a dependency, it
may be appropriate to use a version requirement.
* ``REQUIRED`` for non-optional dependencies which should abort the build
if not found.
* ``QUIET`` for optional dependencies which should not report anything to
users when not found.
:command:`find_package` reports its results via ``<PackageName>_FOUND``
variables, which will be set to a true or false value for found and not found
packages respectively.
Goal
----
Integrate an externally installed test framework into the Tutorial project.
Helpful Resources
-----------------
* :command:`find_package`
* :command:`target_link_libraries`
Files to Edit
-------------
* ``TutorialProject/CMakePresets.json``
* ``TutorialProject/Tests/CMakeLists.txt``
* ``TutorialProject/Tests/TestMathFunctions.cxx``
Getting Started
---------------
The ``Step10`` folder is organized differently than previous steps. The tutorial
project we need to edit is under ``Step10/TutorialProject``. Another project
is now present, ``SimpleTest``, as well as a partially populated install tree
which we will use in later exercises. You do not need to edit anything in these
other directories for this exercise, all ``TODOs`` and solution steps are for
``TutorialProject``.
The ``SimpleTest`` package provides two useful constructs, the
``SimpleTest::SimpleTest`` target to be linked into a test binary, and the
``simpletest_discover_tests`` function for automatically adding tests to
CTest.
Similar to other test frameworks, ``simpletest_discover_tests`` only needs
to be passed the name of the executable target containing the tests.
.. code-block:: cmake
simpletest_discover_tests(MyTestExe)
The ``TestMathFunctions.cxx`` file has been updated to use the ``SimpleTest``
framework in the vein of GoogleTest or Catch2. Perform ``TODO 1`` through
``TODO 5`` in order to use the new test framework.
.. note::
It may go without saying, but ``SimpleTest`` is a very poor test framework
which only facially resembles a functional testing library. While much of
the CMake code in this tutorial could be used unaltered in other projects,
you should not use ``SimpleTest`` outside this tutorial, or try to learn from
the CMake code it provides.
Build and Run
-------------
First we must install the ``SimpleTest`` framework. Navigate to the
``Help/guide/Step10/SimpleTest`` directory and run the following commands
.. code-block:: console
cmake --preset tutorial
cmake --install build
.. note::
The ``SimpleTest`` preset sets up everything needed to install ``SimpleTest``
for the tutorial. For reasons that are beyond the scope of this tutorial,
there is no need to build or provide any other configuration for
``SimpleTest``.
We can observe that the ``Step10/install`` directory has now been populated by
the ``SimpleTest`` header and package files.
Now we can configure and build the Tutorial project as per usual, navigating to
the ``Help/guide/Step10/TutorialProject`` and running:
.. code-block:: console
cmake --preset tutorial
cmake --build build
Verify that the ``SimpleTest`` framework has been consumed correctly by running
the tests with CTest.
Solution
--------
First we call :command:`find_package` to discover the ``SimpleTest`` package.
We do this with ``REQUIRED`` because the tests cannot build without
``SimpleTest``.
.. raw:: html
<details><summary>TODO 1 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
:caption: TODO 1: TutorialProject/Tests/CMakeLists.txt
:name: TutorialProject/Tests/CMakeLists.txt-find_package
:language: cmake
:start-at: find_package
:end-at: find_package
.. raw:: html
</details>
Next we add the ``SimpleTest::SimpleTest`` target to ``TestMathFunctions``
.. raw:: html
<details><summary>TODO 2 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
:caption: TODO 2: TutorialProject/Tests/CMakeLists.txt
:name: TutorialProject/Tests/CMakeLists.txt-link-simple-test
:language: cmake
:start-at: target_link_libraries(TestMathFunctions
:end-at: )
.. raw:: html
</details>
Now we can replace our test description code with a call to
``simpletest_discover_tests``.
.. raw:: html
<details><summary>TODO 3 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
:caption: TODO 3: TutorialProject/Tests/CMakeLists.txt
:name: TutorialProject/Tests/CMakeLists.txt-simpletest_discover_tests
:language: cmake
:start-at: simpletest_discover_tests
:end-at: simpletest_discover_tests
.. raw:: html
</details>
We ensure :command:`find_package` can discover ``SimpleTest`` by
adding the install tree to :variable:`CMAKE_PREFIX_PATH`.
.. raw:: html
<details><summary>TODO 4 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/CMakePresets.json
:caption: TODO 4: TutorialProject/CMakePresets.json
:name: TutorialProject/CMakePresets.json-CMAKE_PREFIX_PATH
:language: json
:start-at: cacheVariables
:end-at: TUTORIAL_ENABLE_IPO
:dedent: 6
:append: }
.. raw:: html
</details>
Finally, we update the tests to use the macros provided by ``SimpleTest`` by
removing the placeholders and including the appropriate header.
.. raw:: html
<details><summary>TODO 5 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tests/TestMathFunctions.cxx
:caption: TODO 5: TutorialProject/Tests/TestMathFunctions.cxx
:name: TutorialProject/Tests/TestMathFunctions.cxx-simpletest
:language: c++
:start-at: #include <MathFunctions.h>
:end-at: {
.. raw:: html
</details>
Exercise 2 - Transitive Dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Libraries often build on one another. A multimedia application may depend on a
library which provides support for various container formats, which may in turn
rely on one or more other libraries for compression algorithms.
We need to express these transitive requirements inside the package config
files we place in the install tree. We do so with the
:module:`CMakeFindDependencyMacro` module, which provides a safe mechanism for
installed packages to recursively discover one another.
.. code-block:: cmake
include(CMakeFindDependencyMacro)
find_dependency(zlib)
:module:`find_dependency() <CMakeFindDependencyMacro>` also forwards arguments
from the top-level :command:`find_package` call. If :command:`find_package` is
called with ``QUIET`` or ``REQUIRED``,
:module:`find_dependency() <CMakeFindDependencyMacro>` will also use ``QUIET``
and/or ``REQUIRED``.
Goal
----
Add a dependency to ``SimpleTest`` and ensure that packages which rely on
``SimpleTest`` also discover this transitive dependency.
Helpful Resources
-----------------
* :module:`CMakeFindDependencyMacro`
* :command:`find_package`
* :command:`target_link_libraries`
Files to Edit
-------------
* ``SimpleTest/CMakeLists.txt``
* ``SimpleTest/cmake/SimpleTestConfig.cmake``
Getting Started
---------------
For this step we will only be editing the ``SimpleTest`` project. The transitive
dependency, ``TransitiveDep``, is a dummy dependency which provides no behavior.
However CMake doesn't know this and the ``TutorialProject`` tests will fail to
configure and build if CMake cannot find all required dependencies.
The ``TransitiveDep`` package has already been installed to the
``Step10/install`` tree. We do not need to install it as we did with
``SimpleTest``.
Complete ``TODO 6`` through ``TODO 8``.
Build and Run
-------------
We need to reinstall the SimpleTest framework. Navigate to the
``Help/guide/Step10/SimpleTest`` directory and run the same commands as before.
.. code-block:: console
cmake --preset tutorial
cmake --install build
Now we can reconfigure and rebuild the ``TutorialProject``, navigate to
``Help/guide/Step10/TutorialProject`` and perform the usual steps to do so.
.. code-block:: console
cmake --preset tutorial
cmake --build build
If the build passed we have likely successfully propagated the transitive
dependency. Verify this by searching the ``CMakeCache.txt`` of
``TutorialProject`` for an entry named ``TransitiveDep_DIR``. This demonstrates
the ``TutorialProject`` searched for an found ``TransitiveDep`` even though it
has no direct requirement for it.
Solution
--------
First we call :command:`find_package` to discover the ``TransitiveDep`` package.
We use ``REQUIRED`` to verify we have found ``TransitiveDep``.
.. raw:: html
<details><summary>TODO 6 Click to show/hide answer</summary>
.. literalinclude:: Step11/SimpleTest/CMakeLists.txt
:caption: TODO 6: SimpleTest/CMakeLists.txt
:name: SimpleTest/CMakeLists.txt-find_package
:language: cmake
:start-at: find_package
:end-at: find_package
.. raw:: html
</details>
Next we add the ``TransitiveDep::TransitiveDep`` target to ``SimpleTest``.
.. raw:: html
<details><summary>TODO 7 Click to show/hide answer</summary>
.. literalinclude:: Step11/SimpleTest/CMakeLists.txt
:caption: TODO 7: SimpleTest/CMakeLists.txt
:name: SimpleTest/CMakeLists.txt-link-transitive-dep
:language: cmake
:start-at: target_link_libraries(SimpleTest
:end-at: )
.. raw:: html
</details>
.. note::
If we built ``TutorialProject`` at this point, we would expect the
configuration to fail due to the ``TransitiveDep::TransitiveDep`` target
being unavailable inside that project.
Finally, we include the :module:`CMakeFindDependencyMacro` and call
:module:`find_dependency() <CMakeFindDependencyMacro>` inside the ``SimpleTest``
package config file to propagate the transitive dependency.
.. raw:: html
<details><summary>TODO 8 Click to show/hide answer</summary>
.. literalinclude:: Step11/SimpleTest/cmake/SimpleTestConfig.cmake
:caption: TODO 8: SimpleTest/cmake/SimpleTestConfig.cmake
:name: SimpleTest/cmake/SimpleTestConfig.cmake-find_dependency
:language: cmake
:start-at: include
:end-at: find_dependency
.. raw:: html
</details>
</details>
Exercise 3 - Finding Other Kinds of Files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In a perfect world every dependency we care about would be packaged correctly,
or at least some other developer would have written a module that discovers it
for us. We do no live in a perfect world, and sometimes we will have to get
our hands dirty and discover build requirements manually.
For this we have the other find commands enumerated earlier in the step, such
as :command:`find_path`.
.. code-block:: cmake
find_path(PackageIncludeFolder Package.h REQUIRED
PATH_SUFFIXES
Package
)
target_include_directories(MyApp
PRIVATE
${PackageIncludeFolder}
)
Goal
----
Add an unpackaged header to the ``Tutorial`` executable of the
``TutorialProject``.
Helpful Resources
-----------------
* :command:`find_path`
* :command:`target_include_directories`
Files to Edit
-------------
* ``TutorialProject/Tutorial/CMakeLists.txt``
* ``TutorialProject/Tutorial/Tutorial.cxx``
Getting Started
---------------
For this step we will only be editing the ``TutorialProject`` project. The
unpackaged header, ``Unpackaged/Unpackaged.h`` has already been installed to the
``Step10/install`` tree.
Complete ``TODO 9`` through ``TODO 11``.
Build and Run
-------------
There are no special build steps for this exercise, navigate to
``Help/guide/Step10/TutorialProject`` and perform the usual build.
.. code-block:: console
cmake --build build
If the build passed we have successfully added the ``Unpackaged`` include
directory to the project.
Solution
--------
First we call :command:`find_path` to discover the ``Unpackaged`` include
directory. We use ``REQUIRED`` because building ``Tutorial`` will fail if
we cannot locate the ``Unpackaged.h`` header.
.. raw:: html
<details><summary>TODO 9 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tutorial/CMakeLists.txt
:caption: TODO 9: TutorialProject/Tutorial/CMakeLists.txt
:name: TutorialProject/Tutorial/CMakeLists.txt-find_path
:language: cmake
:start-at: find_path
:end-at: )
.. raw:: html
</details>
Next we add the discovered path to ``Tutorial`` using
:command:`target_include_directories`.
.. raw:: html
<details><summary>TODO 10 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tutorial/CMakeLists.txt
:caption: TODO 10: TutorialProject/Tutorial/CMakeLists.txt
:name: TutorialProject/Tutorial/CMakeLists.txt-target_include_directories
:language: cmake
:start-at: target_include_directories
:end-at: )
.. raw:: html
</details>
Finally, we edit ``Tutorial.cxx`` to include the discovered header.
.. raw:: html
<details><summary>TODO 11 Click to show/hide answer</summary>
.. literalinclude:: Step11/TutorialProject/Tutorial/Tutorial.cxx
:caption: TODO 11: TutorialProject/Tutorial/Tutorial.cxx
:name: TutorialProject/Tutorial/Tutorial.cxx-include-unpackaged
:language: c++
:start-at: #include <MathFunctions.h>
:end-at: #include <Unpackaged.h>
.. raw:: html
</details>

View File

@@ -0,0 +1,805 @@
Step 1: Getting Started with CMake
==================================
This first step in the CMake tutorial is intended as a quick-start into writing
useful builds for small projects with CMake. By the end, you will be able to
describe executables, libraries, source and header files, and the linkage
relationships between them using CMake.
Each exercise in this step will start with a discussion of the concepts and
commands needed for the exercise. Then, a goal and list of helpful resources are
provided. Each file in the ``Files to Edit`` section is in the ``Step1``
directory and contains one or more ``TODO`` comments. Each ``TODO`` represents
a line or two of code to change or add. The ``TODOs`` are intended to be
completed in numerical order, first complete ``TODO 1`` then ``TODO 2``, etc.
.. note::
Each step in the tutorial builds on the previous, but the steps are not
strictly contiguous. Code not relevant to learning CMake, such as C++
function implementations or CMake code outside the scope of the tutorial,
will sometimes be added between steps.
The ``Getting Started`` section will give some helpful hints and guide you
through the exercise. Then the ``Build and Run`` section will walk step-by-step
through how to build and test the exercise. Finally, at the end of each exercise
the intended solution is reviewed.
Background
^^^^^^^^^^
Typical usage of CMake revolves around one or more files named
``CMakeLists.txt``. This file is sometimes referred to as a "lists file" or
"CML". Within a given software project, a ``CMakeLists.txt`` will exist within
any directory where we want to provide instructions to CMake on how to handle
files and operations local to that directory or subdirectories. Each consists of
a set of commands which describe some information or actions relevant to
building the software project.
Not every directory in a software project needs a CML, but it's strongly
recommended that the project root contains one. This will serve as the entry
point for CMake for its initial setup during configuration. This *root* CML
should always contain the same two commands at or near the top the file.
.. code-block:: cmake
cmake_minimum_required(VERSION 3.23)
project(MyProjectName)
The :command:`cmake_minimum_required` is a compatibility guarantee provided by
CMake to the project developer. When called, it ensures that CMake will adopt
the behavior of the listed version. If a later version of CMake is invoked on a
CML containing the above code, it will act exactly as if it were CMake 3.23.
The :command:`project` command is a conceptually simple command which provides a
complex function. It informs CMake that what follows is the description of a
distinct software project of a given name (as opposed to a shell-like script).
When CMake sees the :command:`project` command it performs various checks to
ensure the environment is suitable for building software; such as checking for
compilers and other build tooling, and discovering properties like the
endianness of the host and target machines.
.. note::
While links to complete documentation are provided for every command, it is
not intended the reader understand the full semantics of each CMake command
they use. Effectively learning CMake, like any piece of software, is an
incremental process.
The rest of this tutorial step will be chiefly concerned with the usage of four
more commands. The :command:`add_executable` and :command:`add_library` commands
for describing output artifacts the software project wants to produce, the
:command:`target_sources` command for associating input files with their
respective output artifacts, and the :command:`target_link_libraries` command
for associating output artifacts with one another.
These four commands are the backbone of most CMake usage. As we'll learn, they
are sufficient for describing the majority of a typical project's requirements.
Exercise 1 - Building an Executable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The most basic CMake project is an executable built from a single source code
file. For simple projects like this, a ``CMakeLists.txt`` file with only
four commands is needed.
.. note::
Although upper, lower and mixed case commands are supported by CMake,
lower case commands are preferred and will be used throughout the tutorial.
The first two commands we have already introduced, :command:`cmake_minimum_required`
and :command:`project`. There is no usage of CMake where the first command in a
root CML will be anything other than :command:`cmake_minimum_required`. There
are some advanced usages where :command:`project` might not be the second
command in a CML, but for our purposes it always will be.
The next command we need is :command:`add_executable`.
This command creates a *target*. In CMake lingo, a target is a name the
developer gives to a collection of properties.
Some examples of properties a target might want to keep track of are:
- The artifact kind (executable, library, header collection, etc)
- Source files
- Include directories
- Output name of an executable or library
- Dependencies
- Compiler and linker flags
The mechanisms of CMake are often best understood as describing and manipulating
targets and their properties. There are many more properties than those listed
here. Documentation of CMake commands will often discuss their function in terms
of the target properties they operate on.
Targets themselves are simply names, a handle to this collection of properties.
Using the :command:`add_executable` command is as easy as specifying the name
we want to use for the target.
.. code-block:: cmake
add_executable(MyProgram)
Now that we have a name for our target, we can start associating properties
with it like source files we want to build and link. The primary command for
this is :command:`target_sources`, which takes as arguments a target name
followed by one or more collections of files.
.. code-block:: cmake
target_sources(MyProgram
PRIVATE
main.cxx
)
.. note::
Paths in CMake are generally either absolute, or relative to the
:variable:`CMAKE_CURRENT_SOURCE_DIR`. We haven't talked about variables like
that yet, so you can read this as "relative to the location of the current
CML".
Each collection of files is prefixed by a :ref:`scope keyword <Target Command Scope>`.
We'll discuss the complete semantics of these keywords when we talk about
linking targets together, but the quick explanation is these describe how a
property should be inherited by dependents of our target.
Typically, nothing depends on an executable. Other programs and libraries don't
need to link to an executable, or inherit headers, or anything of that nature.
So the appropriate scope to use here is ``PRIVATE``, which informs CMake that
this property only belongs to ``MyProgram`` and is not inheritable.
.. note::
This rule is true almost everywhere. Outside advanced and esoteric usages,
the scope keyword for executables should *always* be ``PRIVATE``. The same
holds for implementation files generally, regardless of whether the target
is an executable or a library. The only target which needs to "see" the
``.cxx`` files is the target building them.
Goal
----
Understand how to create a simple CMake project with a single executable.
Helpful Resources
-----------------
* :command:`project`
* :command:`cmake_minimum_required`
* :command:`add_executable`
* :command:`target_sources`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
----------------
The source code for ``Tutorial.cxx`` is provided in the
``Help/guide/tutorial/Step1/Tutorial`` directory and can be used to compute the
square root of a number. This file does not need to be edited in this exercise.
In the parent directory, ``Help/guide/tutorial/Step1``, is a ``CMakeLists.txt``
file which you will complete. Start with ``TODO 1`` and work through ``TODO 4``.
Build and Run
-------------
Once ``TODO 1`` through ``TODO 4`` have been completed, we are ready to build
and run our project! First, run the :manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool.
For example, from the command line we could navigate to the
``Help/guide/tutorial/Step1`` directory and invoke CMake for configuration
as follows:
.. code-block:: console
cmake -B build
The :option:`-B <cmake -B>` flag tells CMake to use the given relative
path as the location to generate files and store artifacts during the build
process. If it is omitted, the current working directory is used. It is
generally considered bad practice to do "in-source" builds, placing these
generated files in the source tree itself.
Next, tell CMake to build the project with
:option:`cmake --build <cmake --build>`, passing it the same relative path
we did with the :option:`-B <cmake -B>` flag.
.. code-block:: console
cmake --build build
The ``Tutorial`` executable will be built into the ``build`` directory. For
multi-config generators (e.g. Visual Studio), it might be placed in a
subdirectory such as ``build/Debug``.
Finally, try to use the newly built ``Tutorial``:
.. code-block:: console
Tutorial 4294967296
Tutorial 10
Tutorial
.. note::
Depending on the shell, the correct syntax may be ``Tutorial``,
``./Tutorial``, ``.\Tutorial``, or even ``.\Tutorial.exe``. For simplicity,
the exercises will use ``Tutorial`` throughout.
Solution
--------
As mentioned above, a four command ``CMakeLists.txt`` is all that we need to get
up and running. The first line should be :command:`cmake_minimum_required`, to
set the CMake version as follows:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step3/CMakeLists.txt
:caption: TODO 1: CMakeLists.txt
:name: CMakeLists.txt-cmake_minimum_required
:language: cmake
:start-at: cmake_minimum_required
:end-at: cmake_minimum_required
.. raw:: html
</details>
The next step to make a basic project is to use the :command:`project`
command as follows to set the project name and inform CMake we intend to build
software with this ``CMakeLists.txt``.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step3/CMakeLists.txt
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-project
:language: cmake
:start-at: project
:end-at: project
.. raw:: html
</details>
Now we can setup our executable target for the Tutorial with :command:`add_executable`.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. literalinclude:: Step3/Tutorial/CMakeLists.txt
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-add_executable
:language: cmake
:start-at: add_executable
:end-at: add_executable
.. raw:: html
</details>
Finally, we can associate our source file with the Tutorial executable target
using :command:`target_sources`.
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 4: CMakeLists.txt
:name: CMakeLists.txt-target_sources
target_sources(Tutorial
PRIVATE
Tutorial/Tutorial.cxx
)
.. raw:: html
</details>
Exercise 2 - Building a Library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We only need to introduce one more command to build a library,
:command:`add_library`. This works exactly like :command:`add_executable`, but
for libraries.
.. code-block:: cmake
add_library(MyLibrary)
However, now is a good time to introduce header files. Header files are not
directly built as translation units, which is to say they are not a *build*
requirement. They are a *usage* requirement. We need to know about header files
in order to build other parts of a given target.
As such, header files are described slightly differently than implementation
files like ``tutorial.cxx``. They're also going to need different
:ref:`scope keywords <Target Command Scope>` than the ``PRIVATE`` keyword we
have used so far.
To describe a collection of header files, we're going to use what's known as a
``FILE_SET``.
.. code-block:: cmake
target_sources(MyLibrary
PRIVATE
library_implementation.cxx
PUBLIC
FILE_SET MyHeaders
TYPE HEADERS
BASE_DIRS
include
FILES
include/library_header.h
)
This is a lot of complexity, but we'll go through it point by point. First,
note that we have our implementation file as a ``PRIVATE`` source, same as
with the executable previously. However, we now use ``PUBLIC`` for our
header file. This allows consumers of our library to "see" the library's
header files.
.. note::
We're not quite ready to discuss the full semantics of scope keywords. We'll
cover them more completely in Exercise 3.
Following the scope keyword is a ``FILE_SET``, a collection of files to be
described as a single unit. A ``FILE_SET`` consists of the following parts:
* ``FILE_SET <name>`` is the name of the ``FILE_SET``. This is a handle which
we can use to describe the collection in other contexts.
* ``TYPE <type>`` is the kind of files we are describing. Most commonly this
will be headers, but newer versions of CMake support other types like C++20
modules.
* ``BASE_DIRS`` is the "base" locations for the files. This can be most easily
understood as the locations that will be described to compilers for header
discovery via ``-I`` flags.
* ``FILES`` is the list of files, same as with the implementation sources list
earlier.
This is a lot of information to describe, so there are some useful shortcuts
we can take. Notably, if the ``FILE_SET`` name is the same as the type, we
don't need to provide the ``TYPE`` field.
.. code-block:: cmake
target_sources(MyLibrary
PRIVATE
library_implementation.cxx
PUBLIC
FILE_SET HEADERS
BASE_DIRS
include
FILES
include/library_header.h
)
There are other shortcuts we can take, but we'll discuss those more in later
steps.
Goal
----
Build a library.
Helpful Resources
-----------------
* :command:`add_library`
* :command:`target_sources`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
Continue editing files in the ``Step1`` directory. Start with ``TODO 5`` and
complete through ``TODO 6``.
Build and Run
-------------
Let's build our project again. Since we already created a build directory and
ran CMake for Exercise 1, we can skip to the build step:
.. code-block:: console
cmake --build build
We should be able to see our library created alongside the Tutorial executable.
Solution
--------
We start by adding the library target in the same manner as the the Tutorial
executable.
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 5: CMakeLists.txt
:name: CMakeLists.txt-add_library
:language: cmake
:start-at: add_library
:end-at: add_library
.. raw:: html
</details>
Next we need to describe the source files. For the implementation file,
``MathFunctions.cxx``, this is straight-forward; for the header file
``MathFunctions.h`` we will need to use a ``FILE_SET``.
We can either give this ``FILE_SET`` its own name, or use the shortcut of naming
it ``HEADERS``. For this tutorial, we'll be using the shortcut, but either
solution is valid.
For ``BASE_DIRS`` we need to determine the directory which will allow for the
desired ``#include <MathFunctions.h>`` directive. To achieve this, the
``MathFunctions`` folder itself will be a base directory. We would make a
different choice if the desired include directive were
``#include <MathFunctions/MathFunctions.h>`` or similar.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 6: CMakeLists.txt
:name: CMakeLists.txt-library_sources
target_sources(MathFunctions
PRIVATE
MathFunctions/MathFunctions.cxx
PUBLIC
FILE_SET HEADERS
BASE_DIRS
MathFunctions
FILES
MathFunctions/MathFunctions.h
)
.. raw:: html
</details>
Exercise 3 - Linking Together Libraries and Executables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We're ready to combine our library with our executable, for this we must
introduce a new command, :command:`target_link_libraries`. The name of this
command can be somewhat misleading, as it does a great deal more than just
invoke linkers. It describes relationships between targets generally.
.. code-block:: cmake
target_link_libraries(MyProgram
PRIVATE
MyLibrary
)
We're finally ready to discuss the :ref:`scope keywords <Target Command Scope>`.
There are three of them, ``PRIVATE``, ``INTERFACE``, and ``PUBLIC``. These
describe how properties are made available to targets.
* A ``PRIVATE`` property (also called a "non-interface" property) is only
available to the target which owns it, for example ``PRIVATE`` headers will
only be visible to the target they're attached to.
* An ``INTERFACE`` property is only available to targets *which link* the
owning target. The owning target does not have access to these properties. A
header-only library is an example of a collection of ``INTERFACE`` properties,
as header-only libraries do not build anything themselves and do not need to
access their own files.
* ``PUBLIC`` is not a distinct kind of property, but rather is the union of the
``PRIVATE`` and ``INTERFACE`` properties. Thus requirements described with
``PUBLIC`` are available to both the owning target and consuming targets.
Consider the following concrete example:
.. code-block:: cmake
target_sources(MyLibrary
PRIVATE
FILE_SET InternalOnlyHeaders
TYPE HEADERS
FILES
InternalOnlyHeader.h
INTERFACE
FILE_SET ConsumerOnlyHeaders
TYPE HEADERS
FILES
ConsumerOnlyHeader.h
PUBLIC
FILE_SET PublicHeaders
TYPE HEADERS
FILES
PublicHeader.h
)
.. note::
We excluded ``BASE_DIRS`` for each file set here, that's another shortcut.
When excluded, ``BASE_DIRS`` defaults to the current source directory.
The ``MyLibrary`` target has several properties which will be modified by this
call to :command:`target_sources`. Until now we've used the term "properties"
generically, but properties are themselves named values we can reason about.
Two specific properties which will be modified here are :prop_tgt:`HEADER_SETS`
and :prop_tgt:`INTERFACE_HEADER_SETS`, which both contain lists of header file
sets added via :command:`target_sources`.
The value ``InternalOnlyHeaders`` will be added to :prop_tgt:`HEADER_SETS`,
``ConsumerOnlyHeaders`` to :prop_tgt:`INTERFACE_HEADER_SETS`, and
``PublicHeaders`` will be added to both.
When a given target is being built, it will use its own *non-interface*
properties (eg, :prop_tgt:`HEADER_SETS`), combined with the *interface*
properties of any targets it links to (eg, :prop_tgt:`INTERFACE_HEADER_SETS`).
.. note::
**It is not necessary to reason about CMake properties at this level of
detail.** The above is described for completeness. Most of the time you don't
need to be concerned with the specific properties a command is modifying.
Scope keywords have a simple intuition associated with them, when considering
a command from the point of view of the target it is being applied to:
**PRIVATE** is for me, **INTERFACE** is for others, **PUBLIC** is for all of
us.
Goal
----
In the Tutorial executable, use the ``sqrt()`` function provided by the
``MathFunctions`` library.
Helpful Resources
-----------------
* :command:`target_link_libraries`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``Tutorial/Tutorial.cxx``
Getting Started
---------------
Continue to edit files from ``Step1``. Start on ``TODO 7`` and complete through
``TODO 9``. In this exercise, we need to add the ``MathFunctions`` target to
the ``Tutorial`` target's linked libraries using :command:`target_link_libraries`.
After modifying the CML, update ``tutorial.cxx`` to use the
``mathfunctions::sqrt()`` function instead of ``std::sqrt``.
Build and Run
-------------
Let's build our project again. As before, we already created a build directory
and ran CMake so we can skip to the build step:
.. code-block:: console
cmake --build build
Verify that the output matches what you would expect from the ``MathFunctions``
library.
Solution
--------
In this exercise, we are describing the ``Tutorial`` executable as a consumer
of the ``MathFunctions`` target by adding ``MathFunctions`` to the linked
libraries of the ``Tutorial``.
To achieve this, we modify ``CMakeLists.txt`` file to use the
:command:`target_link_libraries` command, using ``Tutorial`` as the target to
be modified and ``MathFunctions`` as the library we want to add.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step3/Tutorial/CMakeLists.txt
:caption: TODO 7: CMakeLists.txt
:name: CMakeLists.txt-target_link_libraries
:language: cmake
:start-at: target_link_libraries(Tutorial
:end-at: )
.. raw:: html
</details>
.. note::
The order here is only loosely relevant. That we call
:command:`target_link_libraries` prior to defining ``MathFunctions`` with
:command:`add_library` doesn't matter to CMake. We are recording that
``Tutorial`` has a dependency on something named ``MathFunctions``, but what
``MathFunctions`` means isn't resolved at this stage.
The only target which needs to be defined when calling a CMake command like
:command:`target_sources` or :command:`target_link_libraries` is the target
being modified.
Finally, all that's left to do is modify ``Tutorial.cxx`` to use the newly
provided ``mathfunctions::sqrt`` function. That means adding the appropriate
header file and modifying our ``sqrt()`` call.
.. raw:: html
<details><summary>TODO 8-9: Click to show/hide answer</summary>
.. literalinclude:: Step3/Tutorial/Tutorial.cxx
:caption: TODO 8: Tutorial/Tutorial.cxx
:name: Tutorial/Tutorial.cxx-MathFunctions-headers
:language: c++
:start-at: iostream
:end-at: MathFunctions.h
.. literalinclude:: Step3/Tutorial/Tutorial.cxx
:caption: TODO 9: Tutorial/Tutorial.cxx
:name: Tutorial/Tutorial.cxx-MathFunctions-code
:language: c++
:start-at: calculate square root
:end-at: mathfunctions::sqrt
:dedent: 2
.. raw:: html
</details>
Exercise 4 - Subdirectories
^^^^^^^^^^^^^^^^^^^^^^^^^^^
As we move through the tutorial, we will be adding more commands to manipulate
the ``Tutorial`` executable and the ``MathFunctions`` library. We want to make
sure we keep commands local to the files they are dealing with. While not a
major concern for a small project like this, it can be very useful for large
projects with many targets and thousands of files.
The :command:`add_subdirectory` command allows us to incorporate CMLs located
in subdirectories of the project.
.. code-block:: cmake
add_subdirectory(SubdirectoryName)
When a ``CMakeLists.txt`` in a subdirectory is being processed by CMake all
relative paths described in the subdirectory CML are relative to that
subdirectory, not the top-level CML.
Goal
----
Use :command:`add_subdirectory` to organize the project.
Helpful Resources
-----------------
* :command:`add_subdirectory`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``Tutorial/CMakeLists.txt``
* ``MathFunctions/CMakeLists.txt``
Getting Started
---------------
The ``TODOs`` for this step are spread across three ``CMakeLists.txt`` files.
Be sure to pay attention to the path changes necessary when moving the
:command:`target_sources` commands into subdirectories.
.. note::
Previously we said that ``BASE_DIRS`` defaults to the current source
directory. As the desired include directory for ``MathFunctions`` will now be
the same directory as the CML calling :command:`target_sources`, we should
remove the ``BASE_DIRS`` keyword and argument entirely.
Complete ``TODO 10`` through ``TODO 13``.
Build and Run
-------------
Because of the reorganization, we'll need to clean the original build
directory prior to rebuilding (otherwise our new ``Target`` build folder would
conflict with our previously created ``Target`` executable). We can achieve
this with the :option:`--clean-first <cmake--build --clean-first>` flag.
There's no need for a reconfiguration. CMake will automatically
re-configure itself due to the changes in the CMLs.
.. code-block:: console
cmake --build build --clean-first
.. note::
Our executable and library will be output to a new location in the build tree.
A subdirectory which mirrors where :command:`add_executable` and
:command:`add_library` were called in the source tree. You will need to
navigate to this subdirectory in the build tree to run the tutorial
executable in future steps.
You can verify this behavior by deleting the old ``Tutorial`` executable,
and observing that the new one is produced at ``Tutorial/Tutorial``.
Solution
--------
We need to move all the commands concerning the ``Tutorial`` executable into
``Tutorial/CMakeLists.txt``, and replace them with an
:command:`add_subdirectory` command. We also need to update the path for
``Tutorial.cxx``.
.. raw:: html
<details><summary>TODO 10-11: Click to show/hide answer</summary>
.. literalinclude:: Step3/Tutorial/CMakeLists.txt
:caption: TODO 10: Tutorial/CMakeLists.txt
:name: Tutorial/CMakeLists.txt-moved
:language: cmake
.. code-block:: cmake
:caption: TODO 11: CMakeLists.txt
:name: CMakeLists.txt-add_subdirectory-Tutorial
add_subdirectory(Tutorial)
.. raw:: html
</details>
We need to do the same with the commands for ``MathFunctions``, changing the
relative paths as appropriate and removing ``BASE_DIRS`` as it is no longer
necessary, the default value will work.
.. raw:: html
<details><summary>TODO 12-13: Click to show/hide answer</summary>
.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
:caption: TODO 12: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-moved
:language: cmake
.. literalinclude:: Step3/CMakeLists.txt
:caption: TODO 13: CMakeLists.txt
:name: CMakeLists.txt-add_subdirectory-MathFunctions
:language: cmake
:start-at: add_subdirectory(MathFunctions
:end-at: add_subdirectory(MathFunctions
.. raw:: html
</details>

View File

@@ -0,0 +1,446 @@
Step 5: In-Depth CMake Library Concepts
=======================================
While executables are mostly one-size-fits-all, libraries come in many
different forms. There are static archives, shared objects, modules,
object libraries, header-only libraries, and libraries which describe advanced
CMake properties to be inherited by other targets, just to name a few.
In this step you will learn about some of the most common kinds of libraries
that CMake can describe. This will cover most of the in-project uses of
:command:`add_library`. Libraries which are imported from dependencies (or
exported by the project to be consumed as a dependency) will be covered in
later steps.
Background
^^^^^^^^^^
As we learned in ``Step1``, the :command:`add_library` command accepts the name
of the library target to be created as its first argument. The second
argument is an optional ``<type>`` for which the following values are valid:
``STATIC``
A :ref:`Static Library <Static Libraries>`:
an archive of object files for use when linking other targets.
``SHARED``
A :ref:`Shared Library <Shared Libraries>`:
a dynamic library that may be linked by other targets and loaded
at runtime.
``MODULE``
A :ref:`Module Library <Module Libraries>`:
a plugin that may not be linked by other targets, but may be
dynamically loaded at runtime using dlopen-like functionality.
``OBJECT``
An :ref:`Object Library <Object Libraries>`:
a collection of object files which have not been archived or linked
into a library.
``INTERFACE``
An :ref:`Interface Library <Interface Libraries>`:
a library target which specifies usage requirements for dependents but
does not compile sources and does not produce a library artifact on disk.
In addition, there are ``IMPORTED`` libraries which describe library targets
from foreign projects or modules, imported into the current project. We will
cover these briefly in later steps.
``MODULE`` libraries are most commonly found in plugin systems, or as extensions
to runtime-loading languages like Python or Javascript. They act very similar to
normal shared libraries, except they cannot be directly linked by other targets.
They are sufficiently similar that we won't cover them in further depth here.
Exercise 1 - Static and Shared
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
While the :command:`add_library` command supports explicitly setting ``STATIC``
or ``SHARED``, and this is sometimes necessary, it is best to leave the second
argument empty for most "normal" libraries which can operate as either.
When not given a type, :command:`add_library` will create either a ``STATIC``
or ``SHARED`` library depending on the value of :variable:`BUILD_SHARED_LIBS`.
If :variable:`BUILD_SHARED_LIBS` is true, a ``SHARED`` library will be created,
otherwise it will be ``STATIC``.
.. code-block:: cmake
add_library(MyLib-static STATIC)
add_library(MyLib-shared SHARED)
# Depends on BUILD_SHARED_LIBRARY
add_library(MyLib)
This is desirable behavior, as it allows packagers to determine what kind of
library will be produced, and ensure dependents link to that version of the
library without needing to modify their source code. In some contexts, fully
static builds are appropriate, and in others shared libraries are desirable.
.. note::
CMake does not define the :variable:`BUILD_SHARED_LIBS` variable by default,
meaning without project or user intervention :command:`add_library` will
produce ``STATIC`` libraries.
By leaving the second argument to :command:`add_library()` blank, projects
provide additional flexibility to their packagers and downstream dependents.
Goal
----
Build ``MathFunctions`` as a shared library.
.. note::
On Windows, you might see warnings about an empty DLL, as ``MathFunctions``
doesn't export any symbols.
Helpful Resources
-----------------
* :variable:`BUILD_SHARED_LIBS`
Files to Edit
-------------
There are no files to edit.
Getting Started
---------------
The ``Help/guide/tutorial/Step5`` directory contains the complete, recommended
solution to ``Step4``. This step is about building the ``MathFunctions``
library, there are no ``TODOs`` necessary. You can proceed directly to the
build step.
Build and Run
-------------
We can configure using our preset, turning on :variable:`BUILD_SHARED_LIBS` with
a :option:`-D <cmake -D>` flag.
.. code-block:: console
cmake --preset tutorial -DBUILD_SHARED_LIBS=ON
Then we can build only the ``MathFunctions`` library with
:option:`-t <cmake--build -t>`.
.. code-block:: console
cmake --build build -t MathFunctions
Verify a shared library is produced for ``MathFunctions`` then reset
:variable:`BUILD_SHARED_LIBS`, either by reconfiguring with
``-DBUILD_SHARED_LIBS=OFF`` or deleting the ``CMakeCache.txt``.
Solution
--------
There are no changes to the project for this exercise.
Exercise 2 - Interface Libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Interface libraries are those which only communicate usage requirements for
other targets, they do not build or produce any artifacts of their own. As such
all the properties of an interface library must themselves be interface
properties, specified with the ``INTERFACE`` :ref:`scope keywords <Target Command Scope>`.
.. code-block:: cmake
add_library(MyInterface INTERFACE)
target_compile_definitions(MyInterface INTERFACE MYINTERFACE_COMPILE_DEF)
The most common kind of interface library in C++ development is a header-only
library. Such libraries do not build anything, only providing the flags
necessary to discover their headers.
Goal
----
Add a header-only library to the tutorial project, and use it inside the
``Tutorial`` executable.
Helpful Resources
-----------------
* :command:`add_library`
* :command:`target_sources`
Files to Edit
-------------
* ``MathFunctions/MathLogger/CMakeLists.txt``
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/MathFunctions.cxx``
Getting Started
---------------
In our previous discussions of :command:`target_sources(FILE_SET)`, we noted
we can omit the ``TYPE`` parameter if the file set's name is the same as the
file set's type. We also said we can omit the ``BASE_DIRS`` parameter if
we want to use the current source directory as the only base directory.
We're ready to introduce a third shortcut, we only need to include the ``FILES``
parameter if the headers are intended to be installed, such as public headers
of a library.
The ``MathLogger`` headers in this exercise are only used internally by the
``MathFunctions`` implementation. They will not be installed. This should
make for a very abbreviated call to :command:`target_sources(FILE_SET)`.
.. note::
The headers will be discovered by the compiler's dependency scanner to ensure
correct incremental builds. It can be useful to list header files in these
contexts anyway, as the list can be used to generate metadata some IDEs
rely on.
You can begin editing the ``Step5`` directory. Complete ``TODO 1`` through
``TODO 7``.
Build and Run
-------------
The preset has already been updated to use ``mathfunctions::sqrt`` instead of
``std::sqrt``. We can build and configure as usual.
.. code-block:: console
cmake --preset tutorial
cmake --build build
Verify that the ``Tutorial`` output now uses the logging framework.
Solution
--------
First we add a new ``INTERFACE`` library named ``MathLogger``.
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/MathLogger/CMakeLists.txt
:caption: TODO 1: MathFunctions/MathLogger/CMakeLists.txt
:name: MathFunctions/MathLogger/CMakeLists.txt-add_library
:language: cmake
:start-at: add_library
:end-at: add_library
.. raw:: html
</details>
Then we add the appropriate :command:`target_sources` call to capture the
header information. We give this file set the name ``HEADERS`` so we can
omit the ``TYPE``, we don't need ``BASE_DIRS`` as we will use the default
of the current source directory, and we can exclude the ``FILES`` list because
we don't intend to install the library.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/MathLogger/CMakeLists.txt
:caption: TODO 2: MathFunctions/MathLogger/CMakeLists.txt
:name: MathFunctions/MathLogger/CMakeLists.txt-target_sources
:language: cmake
:start-at: target_sources(
:end-at: )
.. raw:: html
</details>
Now we can add the ``MathLogger`` library to the ``MathFunctions`` linked
libraries, and at the ``MathLogger`` folder to the project.
.. raw:: html
<details><summary>TODO 3-4: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 3: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-link-mathlogger
:language: cmake
:start-at: target_link_libraries(
:end-at: MathLogger
:append: )
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 4: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add-mathlogger
:language: cmake
:start-at: add_subdirectory(MathLogger
:end-at: add_subdirectory(MathLogger
.. raw:: html
</details>
Finally we can update ``MathFunctions.cxx`` to take advantage of the new logger.
.. raw:: html
<details><summary>TODO 5-7: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/MathFunctions.cxx
:caption: TODO 5: MathFunctions/MathFunctions.cxx
:name: MathFunctions/MathFunctions.cxx-mathlogger-header
:language: c++
:start-at: cmath
:end-at: MathLogger
.. literalinclude:: Step6/MathFunctions/MathFunctions.cxx
:caption: TODO 6: MathFunctions/MathFunctions.cxx
:name: MathFunctions/MathFunctions.cxx-mathlogger-logger
:language: c++
:start-at: mathlogger::Logger Logger
:end-at: mathlogger::Logger Logger
.. literalinclude:: Step6/MathFunctions/MathFunctions.cxx
:caption: TODO 7: MathFunctions/MathFunctions.cxx
:name: MathFunctions/MathFunctions.cxx-mathlogger-code
:language: c++
:start-at: Logger.Log(std::format("Computing sqrt of {} to be {}\n"
:end-at: std::format
:dedent: 4
.. raw:: html
</details>
Exercise 3 - Object Libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Object libraries have several advanced uses, but also tricky nuances which
are difficult to fully enumerate in the scope of this tutorial.
.. code-block:: cmake
add_library(MyObjects OBJECT)
The most obvious drawback to object libraries is the objects themselves cannot
be transitively linked. If an object library appears in the
:prop_tgt:`INTERFACE_LINK_LIBRARIES` of a target, the dependents which link that
target will not "see" the objects. The object library will act like an
``INTERFACE`` library in such contexts. In the general case, object libraries
are only suitable for ``PRIVATE`` or ``PUBLIC`` consumption via
:command:`target_link_libraries`.
A common use case for object libraries is coalescing several library targets
into a single archive or shared library object. Even within a single project
libraries may be maintained as different targets for a variety of reasons, such
as belonging to different teams within an organization. However, it may be
desirable to distribute these as a single consumer-facing binary. Object
libraries make this possible.
Goal
----
Add several object libraries to the ``MathFunctions`` library.
Helpful Resources
-----------------
* :command:`target_link_libraries`
* :command:`add_subdirectory`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/MathFunctions.h``
* ``Tutorial/Tutorial.cxx``
Getting Started
---------------
Several extensions for our ``MathFunctions`` library have been made available
(we can imagine these coming from other teams in our organization). Take
a minute to look at the targets made available in ``MathFunctions/MathExtensions``.
Then complete ``TODO 8`` through ``TODO 11``.
Build and Run
-------------
There's no reconfiguration needed, we can build as usual.
.. code-block:: console
cmake --build build
Verify the output of ``Tutorial`` now includes the verification message. Also
take a minute to inspect the build directory under
``build/MathFunctions/MathExtensions``. You should find that, unlike
``MathFunctions``, no archives are produced for any of the object libraries.
Solution
--------
First we will add links for all the object libraries to ``MathFunctions``.
These are ``PUBLIC``, because we want the objects to be added to the
``MathFunctions`` library as part of its own build step, and we want the
headers to be available to consumers of the library.
Then we add the ``MathExtensions`` subdirectoy to the project.
.. raw:: html
<details><summary>TODO 8-9: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 8: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-link-objects
:language: cmake
:start-at: target_link_libraries(
:end-at: )
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 9: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-add-objs
:language: cmake
:start-at: add_subdirectory(MathExtensions
:end-at: add_subdirectory(MathExtensions
.. raw:: html
</details>
To make the extensions available to consumers, we include their headers in the
``MathFunctions.h`` header.
.. raw:: html
<details><summary>TODO 10: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/MathFunctions.h
:caption: TODO 10: MathFunctions/MathFunctions.h
:name: MathFunctions/MathFunctions.h-include-objects
:language: c++
:start-at: OpAdd
:end-at: OpSub
.. raw:: html
</details>
Finally we can take advantage of the extensions in the ``Tutorial`` program.
.. raw:: html
<details><summary>TODO 11: Click to show/hide answer</summary>
.. literalinclude:: Step6/Tutorial/Tutorial.cxx
:caption: TODO 11: Tutorial/Tutorial.cxx
:name: Tutorial/Tutorial.cxx-use-objects
:language: c++
:start-at: OpMul
:end-at: checkValue);
:dedent: 2
.. raw:: html
</details>

View File

@@ -0,0 +1,532 @@
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:: c++
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>

View File

@@ -0,0 +1,418 @@
Step 6: In-Depth System Introspection
=====================================
In order to discover information about the system environment and the toolchain,
CMake will often compile small test programs to verify the availability of
compiler flags, headers, and builtins or other language constructs.
In this step, we will take advantage of the same test program mechanisms that
CMake uses in our own project code.
Background
^^^^^^^^^^
An old trick going back to the oldest days of configuration and build systems
is to verify the availability of some feature by compiling a small program
which uses that feature.
CMake makes this unnecessary for many contexts. As we will address in later
steps, if CMake can find a library dependency, we can rely on it having all
the facilities (headers, code generators, test utilities, etc) we expect it to
have. Conversely, if CMake can't find a dependency, attempting to use the
dependency anyway will almost certainly fail.
However, there are other kinds of information about the toolchain which CMake
doesn't communicate readily. For these advanced cases, we can write our own
test programs and compile commands to check for availability.
CMake provides modules to simplify these checks. These are documented at
:manual:`cmake-modules(7)`. Any module that begins with ``Check`` is a system
introspection module we can use to interrogate the toolchain and system
environment. Some notable ones include:
``CheckIncludeFiles``
Check one or more C/C++ header files.
``CheckCompilerFlag``
Check whether the compiler supports a given flag.
``CheckSourceCompiles``
Checks whether source code can be built for a given language.
``CheckIPOSupported``
Check whether the compiler supports interprocedural optimization (IPO/LTO).
Exercise 1 - Check Include File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A fast and easy check to perform is if a given header file is available on
a certain platform, for which CMake provides :module:`CheckIncludeFiles`. This
is most appropriate for system and intrinsic headers, which may not be provided
by a specific package by are expected to be available in many build environments.
.. code-block:: cmake
include(CheckIncludeFiles)
check_include_files(sys/socket.h HAVE_SYS_SOCKET_H LANGUAGE CXX)
.. note::
These functions are not immediately available in CMake, they must be added via
:command:`include`'ing their associated module (aka, a CMakeLang file). Many
modules live inside CMake's own ``Modules`` folder. This built-in ``Modules``
folder is one of the places CMake searches when evaluating an :command:`include`
command. You can think of these modules like standard library headers, they're
expected to be available.
Once a header file is known to exist, we can communicate that to our code using
the same mechanisms of conditionals and target commands already covered.
Goal
----
Check if the x86 SSE2 intrinsic header is available, and if so use it to
improve ``mathfunctions::sqrt``.
Helpful Resources
-----------------
* :module:`CheckIncludeFiles`
* :command:`target_compile_definitions`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``MathFunctions/MathFunctions.cxx``
Getting Started
---------------
The ``Help/guide/tutorial/Step6`` directory contains the complete, recommended
solution to ``Step5`` and relevant ``TODOs`` for this step. It also contains
specialized implementations of the ``sqrt`` function for various conditions,
which you will find in ``MathFunctions/MathFunctions.cxx``.
Complete ``TODO 1`` through ``TODO 3``. Note that some ``#ifdef`` directives
have already been added to the library, which will change its operation as we
work through the step.
Build and Run
-------------
We can use our usual commands to configure.
.. code-block:: console
cmake --preset tutorial
cmake --build build
In the output of the configuration step we should observe CMake checking for
the ``emmintrin.h`` header.
.. code-block:: console
-- Looking for include file emmintrin.h
-- Looking for include file emmintrin.h - found
If the header is available on your system, verify the ``Tutorial`` output
contains the message about using SSE2. Conversely, if the header is not
available you should see the usual behavior from ``Tutorial``.
Solution
--------
First we include and use the ``CheckIncludeFiles`` module, verifying the
``emmintrin.h`` header is available.
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step7/MathFunctions/CMakeLists.txt
:caption: TODO 1: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-check-include-files
:language: cmake
:start-at: include(CheckIncludeFiles
:end-at: check_include_files(
.. raw:: html
</details>
Then we use the result of the check to conditionally set a compile definition
on ``MathFunctions``.
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step7/MathFunctions/CMakeLists.txt
:caption: TODO 2: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-define-use-sse2
:language: cmake
:start-at: if(HAS_EMMINTRIN)
:end-at: endif()
.. raw:: html
</details>
Finally we can conditionally include the header in the ``MathFunctions`` library.
.. raw:: html
<details><summary>TODO 3: Click to show/hide answer</summary>
.. literalinclude:: Step7/MathFunctions/MathFunctions.cxx
:caption: TODO 3: MathFunctions/MathFunctions.cxx
:name: MathFunctions/MathFunctions.cxx-include-sse2
:language: c++
:start-at: #ifdef TUTORIAL_USE_SSE2
:end-at: #endif
.. raw:: html
</details>
Exercise 2 - Check Source Compiles
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes it is insufficient to merely check for a header. This is especially
true when no header is available to check, such is the case with
compiler-builtins. For these scenarios we have :module:`CheckSourceCompiles`.
.. code-block:: cmake
include(CheckSourceCompiles)
check_source_compiles(CXX
"
int main() {
int a, b, c;
__builtin_add_overflow(a, b, &c);
}
"
HAS_CHECKED_ADDITION
)
.. note::
By default :module:`CheckSourceCompiles` builds and links an executable. The
code to be check must provide a valid ``int main()`` in order to succeed.
After performing the check, this system introspection can be applied identically
to how we discussed with header files.
Goal
----
Check if the GNU SSE2 builtins are available, and if so use them to improve
``mathfunctions::sqrt``.
Helpful Resources
-----------------
* :module:`CheckSourceCompiles`
* :command:`target_compile_definitions`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
Getting Started
---------------
Complete ``TODO 4`` and ``TODO 5``. No code changes to the ``MathFunctions``
implementation are necessary, as these have already been provided.
Build and Run
-------------
We need only rebuild the tutorial.
.. code-block:: console
cmake --build build
.. note::
If a check fails and you think it should succeed, you will need to clear the
CMake Cache by deleting the ``CMakeCache.txt`` file. CMake will not rerun
compile checks on subsequent runs if it has a cached result.
In the output of the configuration step we should observe CMake checking if the
provided source code compiles, which will be reported under the variable name
we provided to ``check_source_compiles()``.
.. code-block:: console
-- Performing Test HAS_GNU_BUILTIN
-- Performing Test HAS_GNU_BUILTIN - Success
If the builtins are available on your compiler, verify the ``Tutorial`` output
contains the message about using GNU-builting. Conversely, if the builtins are
not available you should see the previous behavior from ``Tutorial``.
Solution
--------
First we include and use the ``CheckSourceCompiles`` module, verifying the
provided source code can be built.
..
pygments doesn't like the [=[ <string> ]=] literals in the following
literalinclude, so use :language: none
.. raw:: html
<details><summary>TODO 4: Click to show/hide answer</summary>
.. literalinclude:: Step7/MathFunctions/CMakeLists.txt
:caption: TODO 4: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-check-source-compiles
:language: none
:start-at: include(CheckSourceCompiles
:end-at: HAS_GNU_BUILTIN
:append: )
.. raw:: html
</details>
Then we use the result of the check to conditionally set a compile definition
on ``MathFunctions``.
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step7/MathFunctions/CMakeLists.txt
:caption: TODO 5: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-define-use-gnu-builtin
:language: cmake
:start-at: if(HAS_GNU_BUILTIN)
:end-at: endif()
.. raw:: html
</details>
Exercise 3 - Check Interprocedural Optimization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Interprocedural and link time optimizations can provide significant performance
improvements to some software. CMake has the capacity to check for the
availability of IPO flags via :module:`CheckIPOSupported`.
.. code-block:: cmake
include(CheckIPOSupported)
check_ipo_supported() # fatal error if IPO is not supported
set_target_properties(MyApp
PROPERTIES
INTERPROCEDURAL_OPTIMIZATION TRUE
)
.. note::
There a couple important caveats with regard to in-project IPO configuration:
* CMake does not know about every IPO/LTO flag on every compiler, better
results can often be achieved with individual tuning for a known toolchain.
* Setting the :prop_tgt:`INTERPROCEDURAL_OPTIMIZATION` property on a target
does not alter any of the targets it links to, or dependencies from other
projects. IPO can only "see" into other targets which are also compiled
appropriately.
For these reasons, serious consideration should be given to manually setting
up IPO/LTO flags across all projects in the dependency tree via external
mechanisms (presets, :option:`-D <cmake -D>` flags,
:manual:`toolchain files <cmake-toolchains(7)>`, etc) instead of in-project
control.
However, especially for extremely large projects, it can be useful to have
an in-project mechanism to use IPO whenever it is available.
Goal
----
Enable IPO for the entire tutorial project when it is available from the
toolchain.
Helpful Resources
-----------------
* :module:`CheckIPOSupported`
* :variable:`CMAKE_INTERPROCEDURAL_OPTIMIZATION`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
Continue editing the files in ``Step6``. Complete ``TODO 6`` and ``TODO 7``.
Build and Run
-------------
We need only rebuild the tutorial.
.. code-block:: console
cmake --build build
If IPO is unavailable, we will see an error message during configuration.
Otherwise nothing will change.
.. note::
Regardless of the result of the IPO check, we shouldn't expect any change
in behavior from ``Tutorial`` or ``MathFunctions``.
Solution
--------
The first ``TODO`` is easy, we add another option to our project.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. literalinclude:: Step7/CMakeLists.txt
:caption: TODO 6: MathFunctions/CMakeLists.txt
:name: CMakeLists.txt-enable-ipo
:language: cmake
:start-at: option(TUTORIAL_ENABLE_IPO
:end-at: option(TUTORIAL_ENABLE_IPO
.. raw:: html
</details>
The next step is involved, however the documentation for :module:`CheckIPOSupported`
has an almost complete example of what we need to do. The only difference is
we are going to enable IPO project-wide instead of for a single target.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step7/CMakeLists.txt
:caption: TODO 7: CMakeLists.txt
:name: CMakeLists.txt-check-ipo
:language: cmake
:start-at: if(TUTORIAL_ENABLE_IPO)
:end-at: endif()
:append: endif()
.. raw:: html
</details>
.. note::
Normally we have discouraged setting ``CMAKE_`` variables inside the project.
Here, we are controlling that behavior with an :command:`option()`. This
allows packagers to opt-out of our override. This is an imperfect, but
acceptable solution to situations where we want to provide options to control
project-wide behavior controlled by ``CMAKE_`` variables.

View File

@@ -0,0 +1,596 @@
Step 9: Installation Commands and Concepts
==========================================
Projects need to do more than build and test their code, they need to make it
available to consumers. The layout of files in the build tree is unsuitable
for consumption by other projects, binaries are in unexpected places, header
files are located far away in the source tree, and there's no clear way
to discover what targets are provided or how to use them.
This translation, moving artifacts from the source and build trees into a final
layout suitable for consumption, is known as installation. CMake supports a
complete installation workflow as part of the project description, controlling
both the layout of artifacts in the install tree, and reconstructing targets
for other CMake projects which want to consume the libraries provided by the
install tree.
Background
^^^^^^^^^^
All CMake installation goes through a single command, :command:`install`, which
is split into many subcommands responsible for various aspects of the
installation process. For target-based CMake workflows, it is mostly sufficient
to rely on installing targets themselves with :command:`install(TARGETS)`
instead of resorting to manually moving files with :command:`install(FILES)`
or :command:`install(DIRECTORY)`.
.. note::
This is why we need to add ``FILES`` to header sets which are intended to be
installed. CMake needs to be able to locate the files when their associated
target is installed.
CMake divides target-based installation into various artifact kinds. The
available artifact kinds (in CMake 3.23) are:
``ARCHIVE``
Static libraries (``.a`` / ``.lib``), DLL import libraries (``.lib``), and
a handful of other "archive-like" objects.
``LIBRARY``
Shared libraries (``.so``), modules, and other dynamically loadable
objects. **Not** Window's DLL files (``.dll``) or MacOS frameworks.
``RUNTIME``
Executables of all kinds except MacOS bundles; and Window's DLLs (``.dll``).
``OBJECT``
Objects from ``OBJECT`` libraries.
``FRAMEWORK``
Both static and shared MacOS frameworks
``BUNDLE``
MacOS bundle executables
``PUBLIC_HEADER`` / ``PRIVATE_HEADER`` / ``RESOURCE``
Files described by the :prop_tgt:`PUBLIC_HEADER`, :prop_tgt:`PRIVATE_HEADER`
and :prop_tgt:`RESOURCE` target properties, typically used with MacOS
frameworks
``FILE_SET <set-name>``
A file set associated with the target. This is how headers are typically
installed.
Most important artifact kinds have known destinations which CMake will default
to unless instructed to do otherwise. For example, ``RUNTIME`` will be installed
to the location named by :module:`CMAKE_INSTALL_BINDIR <GNUInstallDirs>`, if
the variable is available, otherwise they default to ``bin``.
The full list of artifact kind default destinations is described in the
following table.
=============================== =============================== ======================
Target Type Variable Built-In Default
=============================== =============================== ======================
``RUNTIME`` ``${CMAKE_INSTALL_BINDIR}`` ``bin``
``LIBRARY`` ``${CMAKE_INSTALL_LIBDIR}`` ``lib``
``ARCHIVE`` ``${CMAKE_INSTALL_LIBDIR}`` ``lib``
``PRIVATE_HEADER`` ``${CMAKE_INSTALL_INCLUDEDIR}`` ``include``
``PUBLIC_HEADER`` ``${CMAKE_INSTALL_INCLUDEDIR}`` ``include``
``FILE_SET`` (type ``HEADERS``) ``${CMAKE_INSTALL_INCLUDEDIR}`` ``include``
=============================== =============================== ======================
For the most part, projects should leave the defaults alone unless they need to
install to a specific subdirectory of a default location.
CMake does not define the ``CMAKE_INSTALL_<dir>`` variables by default. If a
project wishes to dictate installing to a subdirectory of one of these
locations, it is necessary to include the :module:`GNUInstallDirs` module, which
will provide values for all ``CMAKE_INSTALL_<dir>`` variables that have not
already been defined.
Exercise 1 - Installing Artifacts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For modern, target-based CMake projects installation of artifacts is trivial
and consists of a single call to :command:`install(targets)`.
.. code-block:: cmake
install(
TARGETS MyApp MyLib
FILE_SET HEADERS
FILE_SET AnotherHeaderFileSet
)
Most artifact kinds are installed by default and do not need to be listed in
the :command:`install` command. However, ``FILE_SET``s must be named to let
CMake know you want to install. In the above example we install two file
sets, one named ``HEADERS`` and another named ``AnotherHeaderFileSet``.
When named, an artifact kind can be given various options, such as a destination.
.. code-block:: cmake
include(GNUInstallDirs)
install(
TARGETS MyApp MyLib
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}/Subfolder
FILE_SET HEADERS
)
This will install the ``MyApp`` target to ``bin/Subfolder`` (if the packager
hasn't changed :module:`CMAKE_INSTALL_BINDIR <GNUInstallDirs>`).
Importantly, if the ``OBJECT`` artifact kind is never given a destination, it
will act like an ``INTERFACE`` library, only installing its headers.
Goal
----
Install the artifacts for the libraries and executables (except tests) described
in the tutorial project.
Helpful Resources
-----------------
* :command:`install`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
The ``Help/guide/tutorial/Step9`` directory contains the complete, recommended
solution to ``Step8``. Complete ``TODO 1`` and ``TODO 2``.
Build and Run
-------------
No special configuration is needed, configure and build as usual.
.. code-block:: console
cmake --preset tutorial
cmake --build build
We can verify the installation is correct with :option:`cmake --install`.
.. code-block:: console
cmake --install build --prefix install
The ``install`` folder should be populated correctly for our artifacts.
Solution
--------
First we add an :command:`install(TARGETS)` for the conditionally built,
thus conditionally installed, ``Tutorial`` executable.
.. raw:: html
<details><summary>TODO 1 Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 1: CMakeLists.txt
:name: CMakeLists.txt-install-tutorial
if(TUTORIAL_BUILD_UTILITIES)
add_subdirectory(Tutorial)
install(
TARGETS Tutorial
)
endif()
.. raw:: html
</details>
Then we can install the rest of the targets.
.. raw:: html
<details><summary>TODO 2 Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 2: CMakeLists.txt
:name: CMakeLists.txt-install-libs
install(
TARGETS MathFunctions OpAdd OpMul OpSub MathLogger SqrtTable
FILE_SET HEADERS
)
.. raw:: html
</details>
.. note::
We could add :command:`install(TARGETS)` commands locally to each subfolder
where the targets are defined. This would be typical in very large projects
where keeping track of all the installable targets is difficult.
It might seem unnecessary to install the ``SqrtTable`` and ``MathLogger``,
and it is at this stage. Due to how CMake models target relationships, when we
reconstruct the target model in the next exercise we will need these targets to
be available.
Exercise 2 - Exporting Targets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This raw collection of installed files is a good start, but we lose the CMake
target model. These are effectively no better than the pre-compiled vendored
libraries we discussed in ``Step 4``. We need some way for other projects to
reconstruct our targets from what we have provided in the install tree.
The mechanism CMake provides to solve this is a CMakeLang file known as a
"target export file". It is created by the :command:`install(EXPORT)`
command.
.. code-block:: cmake
install(
TARGETS MyApp MyLib
EXPORT MyProjectTargets
)
include(GNUInstallDirs)
install(
EXPORT MyProjectTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
NAMESPACE MyProject::
)
There are several parts to the above example. Firstly the
:command:`install(TARGETS)` command takes an export name, basically a list to
add the installed targets to.
Later, the :command:`install(EXPORT)` command consumes this list of targets
to generate the target export file. This will be a file named
``<ExportName>.cmake`` located in the provided ``DESTINATION``. The
``DESTINATION`` provided in this example is the conventional one, but any
location searched by the :command:`find_package` command is valid.
Finally, the targets created by the target export file will be prefixed with the
``NAMESPACE`` string, ie they will be of the form ``<NAMESPACE><TargetName>``.
It is conventional for this to be the project name followed by two colons.
For reasons that will become more obvious in future steps, we typically don't
consume this file directly. Instead we have a file named
``<ProjectName>Config.cmake`` consume it via :command:`include()`.
.. code-block:: cmake
include(${CMAKE_CURRENT_LIST_DIR}/MyProjectTargets.cmake)
.. note::
The :variable:`CMAKE_CURRENT_LIST_DIR` variable names the directory that the
currently running CMake Language file is inside of, regardless of how that
file was included or launched.
Then this file is installed alongside the target export with
:command:`install(FILES)`.
.. code-block:: cmake
install(
FILES
cmake/MyProjectConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
)
.. note::
The name of this file and its location are dictated by the discovery
semantics of the :command:`find_package` command, which we will discuss more
in the next step.
Goal
----
Export the Tutorial project targets so other projects may consume them.
Helpful Resources
-----------------
* :command:`install`
* :module:`GNUInstallDirs`
* :variable:`CMAKE_CURRENT_LIST_DIR`
Files to Edit
-------------
* ``CMakeLists.txt``
* ``cmake/TutorialConfig.cmake``
Getting Started
---------------
Continue editing the files in the ``Help/guide/tutorial/Step9`` directory.
Complete ``TODO 3`` through ``TODO 8``.
Build and Run
-------------
The build command is sufficient to reconfigure the project.
.. code-block:: console
cmake --build build
We can verify the installation is correct with :option:`cmake --install`.
.. note::
As with CTest, when using multi-config generator, eg Visual Studio, it will be
necessary to specify a configuration with
``cmake --install --config <config> <remaining flags>``, where
``<config>`` is a value like ``Debug`` or ``Release``. This is true whenever
using a multi-config generator, and won't be called out specifically in
future commands.
.. code-block:: console
cmake --install build --prefix install
.. note::
CMake won't update files which have not changed, only installing new or
updated files from the build and source trees.
The ``install`` folder should be populated correctly for our artifacts and
export files. We'll demonstrate how to use these files in the next step.
Solution
--------
First we add the ``Tutorial`` target to the ``TutorialTargets`` export.
.. raw:: html
<details><summary>TODO 3 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 3: CMakeLists.txt
:name: CMakeLists.txt-install-tutorial-export
:language: cmake
:start-at: install(
:end-at: )
.. raw:: html
</details>
Soon we will need access to the ``CMAKE_INSTALL_<dir>`` variables, so next
we include the :module:`GNUInstallDirs` module.
.. raw:: html
<details><summary>TODO 4 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 4: CMakeLists.txt
:name: CMakeLists.txt-gnuinstalldirss
:language: cmake
:start-at: include(GNUInstallDirs)
:end-at: include(GNUInstallDirs)
.. raw:: html
</details>
Now we add the rest of our targets to the ``TutorialTargets`` export.
.. raw:: html
<details><summary>TODO 5 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 5: CMakeLists.txt
:name: CMakeLists.txt-install-libs-export
:language: cmake
:start-at: TARGETS MathFunctions
:end-at: )
:prepend: install(
.. raw:: html
</details>
Next we install the export itself, to generate our target export file.
.. raw:: html
<details><summary>TODO 6 Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 6: CMakeLists.txt
:name: CMakeLists.txt-install-export
install(
EXPORT TutorialTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
NAMESPACE Tutorial::
)
.. raw:: html
</details>
And then we install our "config" file, which we will use to include our target
export file.
.. raw:: html
<details><summary>TODO 7 Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 7: CMakeLists.txt
:name: CMakeLists.txt-install-config
install(
FILES
cmake/TutorialConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)
.. raw:: html
</details>
Finally we can add the necessary :command:`include` command to the config file.
.. raw:: html
<details><summary>TODO 8 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/cmake/TutorialConfig.cmake
:caption: TODO 8: cmake/TutorialConfig.cmake
:name: cmake/TutorialConfig.cmake
:language: cmake
:start-at: include
:end-at: include
.. raw:: html
</details>
Exercise 3 - Exporting a Version File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When importing CMake targets from a target export file, there is no way to
"bail out" or "undo" the operation. If it turns out a package is a wrong or
incompatible version for the one we requested, we'll be stuck with any
side-effects incurred while we learned that version information.
The answer CMake provides for this problem is a light-weight version file which
only describes this version compatibility information, which can be checked
before CMake commits to fully importing the file.
CMake provides helper modules and scripts for generating these version files,
namely the :module:`CMakePackageConfigHelpers` module.
.. code-block:: cmake
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
COMPATIBILITY ExactVersion
)
The available versions are:
* ``AnyNewerVersion``
* ``SameMajorVersion``
* ``SameMinorVersion``
* ``ExactVersion``
Additionally packages can mark themselves as ``ARCH_INDEPENDENT``, intended for
packages which ship no binaries which would tie them to a specific machine
architecture.
By default, the ``VERSION`` used by ``write_basic_package_version_file()`` is
the ``VERSION`` number given to the :command:`project` command.
Goal
----
Export a version file for the Tutorial project.
Helpful Resources
-----------------
* :command:`project`
* :command:`install`
* :module:`CMakePackageConfigHelpers`
* :variable:`PROJECT_VERSION`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
Continue editing the files in the ``Help/guide/tutorial/Step9`` directory.
Complete ``TODO 9`` through ``TODO 12``.
Build and Run
-------------
Rebuild and install as done previously.
.. code-block:: console
cmake --build build
cmake --install build --prefix install
The ``install`` folder should be populated correctly with our newly generated
and installed version file.
Solution
--------
First we add a ``VERSION`` parameter to the :command:`project` command.
.. raw:: html
<details><summary>TODO 9 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 9: CMakeLists.txt
:name: CMakeLists.txt-project-version
:language: cmake
:start-at: project(
:end-at: )
.. raw:: html
</details>
Next we include the :module:`CMakePackageConfigHelpers` modules and use it
to generate the config version file.
.. raw:: html
<details><summary>TODO 10-11 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 10-11: CMakeLists.txt
:name: CMakeLists.txt-write_basic_package_version_file
:language: cmake
:start-at: include(CMakePackageConfigHelpers
:end-at: COMPATIBILITY ExactVersion
:append: )
.. raw:: html
</details>
Finally we add the config version file to the list of files to be installed.
.. raw:: html
<details><summary>TODO 12 Click to show/hide answer</summary>
.. literalinclude:: Step10/TutorialProject/CMakeLists.txt
:caption: TODO 12: CMakeLists.txt
:name: CMakeLists.txt-install-version-config
:language: cmake
:start-at: FILES
:end-at: )
:prepend: install(
.. raw:: html
</details>

View File

@@ -1,311 +0,0 @@
Step 5: Installing and Testing
==============================
Exercise 1 - Install Rules
^^^^^^^^^^^^^^^^^^^^^^^^^^
Often, it is not enough to only build an executable, it should also be
installable. With CMake, we can specify install rules using the
:command:`install` command. Supporting local installations for your builds in
CMake is often as simple as specifying an install location and the targets and
files to be installed.
Goal
----
Install the ``Tutorial`` executable and the ``MathFunctions`` library.
Helpful Materials
-----------------
* :command:`install`
Files to Edit
-------------
* ``MathFunctions/CMakeLists.txt``
* ``CMakeLists.txt``
Getting Started
---------------
The starting code is provided in the ``Step5`` directory. In this
exercise, complete ``TODO 1`` through ``TODO 4``.
First, update ``MathFunctions/CMakeLists.txt`` to install the
``MathFunctions`` and ``tutorial_compiler_flags`` libraries to the ``lib``
directory. In that same file, specify the install rules needed to install
``MathFunctions.h`` to the ``include`` directory.
Then, update the top level ``CMakeLists.txt`` to install
the ``Tutorial`` executable to the ``bin`` directory. Lastly, any header files
should be installed to the ``include`` directory. Remember that
``TutorialConfig.h`` is in the :variable:`PROJECT_BINARY_DIR`.
Build and Run
-------------
Make a new directory called ``Step5_build``. Run the
:manual:`cmake <cmake(1)>` executable or the
:manual:`cmake-gui <cmake-gui(1)>` to configure the project and then build it
with your chosen build tool.
Then, run the install step by using the :option:`--install <cmake --install>`
option of the :manual:`cmake <cmake(1)>` command (introduced in 3.15, older
versions of CMake must use ``make install``) from the command line. This step
will install the appropriate header files, libraries, and executables.
For example:
.. code-block:: console
cmake --install .
For multi-configuration tools, don't forget to use the
:option:`--config <cmake--build --config>` argument to specify the configuration.
.. code-block:: console
cmake --install . --config Release
If using an IDE, simply build the ``INSTALL`` target. You can build the same
install target from the command line like the following:
.. code-block:: console
cmake --build . --target install --config Debug
The CMake variable :variable:`CMAKE_INSTALL_PREFIX` is used to determine the
root of where the files will be installed. If using the :option:`cmake --install`
command, the installation prefix can be overridden via the
:option:`--prefix <cmake--install --prefix>` argument. For example:
.. code-block:: console
cmake --install . --prefix "/home/myuser/installdir"
Navigate to the install directory and verify that the installed ``Tutorial``
runs.
Solution
--------
The install rules for our project are fairly simple:
* For ``MathFunctions``, we want to install the libraries and header file to
the ``lib`` and ``include`` directories respectively.
* For the ``Tutorial`` executable, we want to install the executable and
configured header file to the ``bin`` and ``include`` directories
respectively.
So to the end of ``MathFunctions/CMakeLists.txt`` we add:
.. raw:: html
<details><summary>TODO 1: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 1: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-install-TARGETS
:language: cmake
:start-after: # install libs
:end-before: # install include headers
.. raw:: html
</details>
and
.. raw:: html
<details><summary>TODO 2: Click to show/hide answer</summary>
.. literalinclude:: Step6/MathFunctions/CMakeLists.txt
:caption: TODO 2: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-install-headers
:language: cmake
:start-after: # install include headers
.. raw:: html
</details>
The install rules for the ``Tutorial`` executable and configured header file
are similar. To the end of the top-level ``CMakeLists.txt`` we add:
.. raw:: html
<details><summary>TODO 3,4: Click to show/hide answer</summary>
.. literalinclude:: Step6/CMakeLists.txt
:caption: CMakeLists.txt
:name: TODO 3,4: CMakeLists.txt-install-TARGETS
:language: cmake
:start-after: # add the install targets
:end-before: # TODO 1: Replace enable_testing() with include(CTest)
.. raw:: html
</details>
That is all that is needed to create a basic local
install of the tutorial.
.. _`Tutorial Testing Support`:
Exercise 2 - Testing Support
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CTest offers a way to easily manage tests for your project. Tests can be
added through the :command:`add_test` command. Although it is not
explicitly covered in this tutorial, there is a lot of compatibility
between CTest and other testing frameworks such as :module:`GoogleTest`.
Goal
----
Create unit tests for our executable using CTest.
Helpful Materials
-----------------
* :command:`enable_testing`
* :command:`add_test`
* :command:`function`
* :command:`set_tests_properties`
* :manual:`ctest <ctest(1)>`
Files to Edit
-------------
* ``CMakeLists.txt``
Getting Started
---------------
The starting source code is provided in the ``Step5`` directory. In this
exercise, complete ``TODO 5`` through ``TODO 9``.
First, we need to enable testing. Next, begin adding tests to our project
using :command:`add_test`. We will work through adding 3 simple tests and
then you can add additional testing as you see fit.
Build and Run
-------------
Navigate to the build directory and rebuild the application. Then, run the
:program:`ctest` executable: :option:`ctest -N` and :option:`ctest -VV`. For
multi-config generators (e.g. Visual Studio), the configuration type must be
specified with the :option:`-C \<mode\> <ctest -C>` flag. For example, to run tests in Debug
mode use ``ctest -C Debug -VV`` from the build directory
(not the Debug subdirectory!). Release mode would be executed from the same
location but with a ``-C Release``. Alternatively, build the ``RUN_TESTS``
target from the IDE.
Solution
--------
Let's test our application. At the end of the top-level ``CMakeLists.txt``
file we first need to enable testing with the
:command:`enable_testing` command.
.. raw:: html
<details><summary>TODO 5: Click to show/hide answer</summary>
.. literalinclude:: Step6/CMakeLists.txt
:caption: TODO 5: CMakeLists.txt
:name: CMakeLists.txt-enable_testing
:language: cmake
:start-after: # enable testing
:end-before: # does the application run
.. raw:: html
</details>
With testing enabled, we will add a number of basic tests to verify
that the application is working correctly. First, we create a test using
:command:`add_test` which runs the ``Tutorial`` executable with the
parameter 25 passed in. For this test, we are not going to check the
executable's computed answer. This test will verify that
application runs, does not segfault or otherwise crash, and has a zero
return value. This is the basic form of a CTest test.
.. raw:: html
<details><summary>TODO 6: Click to show/hide answer</summary>
.. literalinclude:: Step6/CMakeLists.txt
:caption: TODO 6: CMakeLists.txt
:name: CMakeLists.txt-test-runs
:language: cmake
:start-after: # does the application run
:end-before: # does the usage message work
.. raw:: html
</details>
Next, let's use the :prop_test:`PASS_REGULAR_EXPRESSION` test property to
verify that the output of the test contains certain strings. In this case,
verifying that the usage message is printed when an incorrect number of
arguments are provided.
.. raw:: html
<details><summary>TODO 7: Click to show/hide answer</summary>
.. literalinclude:: Step6/CMakeLists.txt
:caption: TODO 7: CMakeLists.txt
:name: CMakeLists.txt-test-usage
:language: cmake
:start-after: # does the usage message work?
:end-before: # define a function to simplify adding tests
.. raw:: html
</details>
The next test we will add verifies the computed value is truly the
square root.
.. raw:: html
<details><summary>TODO 8: Click to show/hide answer</summary>
.. code-block:: cmake
:caption: TODO 8: CMakeLists.txt
:name: CMakeLists.txt-test-standard
add_test(NAME StandardUse COMMAND Tutorial 4)
set_tests_properties(StandardUse
PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2"
)
.. raw:: html
</details>
This one test is not enough to give us confidence that it will
work for all values passed in. We should add more tests to verify this.
To easily add more tests, we make a function called ``do_test`` that runs the
application and verifies that the computed square root is correct for
given input. For each invocation of ``do_test``, another test is added to
the project with a name, input, and expected results based on the passed
arguments.
.. raw:: html
<details><summary>TODO 9: Click to show/hide answer</summary>
.. literalinclude:: Step6/CMakeLists.txt
:caption: TODO 9: CMakeLists.txt
:name: CMakeLists.txt-generalized-tests
:language: cmake
:start-after: # define a function to simplify adding tests
.. raw:: html
</details>

View File

@@ -0,0 +1,188 @@
Step 11: Miscellaneous Features
===============================
Some features don't fit well or aren't important enough to receive attention
in the main tutorial, but deserve mention. These exercises collect some of those
features. They should be considered "bonuses".
There are many CMake features that are not covered by the tutorial, some of
which are considered essential to the projects which use them. Others are
in common use by packagers but see little discussion among software developers
producing local builds.
This list is not an exhaustive discussion of what remains of CMake's
capabilities. It may grow or shrink with time and relevance.
Exercise 1: Target Aliases
^^^^^^^^^^^^^^^^^^^^^^^^^^
This tutorial focuses on installing dependencies and consuming them from an
install tree. It also recommends the use of package managers to facilitate
this process. However, for a variety of reasons both historical and
contemporary this is not always how CMake projects are consumed.
It is possible to vendor a dependency's source code entirely in a parent project
and consume it with :command:`add_subdirectory`. When performed, the target
names exposed are those used within the project, not those exported via
:command:`install(EXPORT)`. The target names will not have the namespace string
that command prefixes to targets.
Some projects wish to support this workflow with an interface consistent with
the one presented to :command:`find_package` consumers. CMake supports this via
:command:`add_library(ALIAS)` and :command:`add_executable(ALIAS)`.
.. code-block:: cmake
add_library(MyLib INTERFACE)
add_library(MyProject::MyLib ALIAS MyLib)
Goal
----
Add a library alias for the ``MathFunctions`` library.
Helpful Resources
-----------------
* :command:`add_library`
Files to Edit
-------------
* ``TutorialProject/MathFunctions/CMakeLists.txt``
Getting Started
---------------
For this step we will only be editing the ``TutorialProject`` project in the
``Step11`` folder. Complete ``TODO 1``.
Build and Run
-------------
To build the project we first need configure and install ``SimpleTest``.
Navigate to ``Help/guide/Step11/SimpleTest`` and run the appropriate commands.
.. code-block:: console
cmake --preset tutorial
cmake --install build
Then navigate to ``Help/guide/Step11/TutorialProject`` and perform the usual build.
.. code-block:: console
cmake --preset tutorial
cmake --build build
There should be no observable change in behavior from adding the alias.
Solution
--------
We add a single line to the ``MathFunctions`` CML.
.. raw:: html
<details><summary>TODO 1 Click to show/hide answer</summary>
.. literalinclude:: Complete/TutorialProject/MathFunctions/CMakeLists.txt
:caption: TODO 1: TutorialProject/MathFunctions/CMakeLists.txt
:name: TutorialProject/MathFunctions/CMakeLists.txt-alias
:language: cmake
:start-at: ALIAS
:end-at: ALIAS
.. raw:: html
</details>
Exercise 2: Generator Expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:manual:`Generator expressions <cmake-generator-expressions(7)>` are a
complicated domain-specific language supported in some contexts within CMake.
They are most easily understood as deferred-evaluation conditionals, they
express requirements where the inputs to determine the correct behavior are not
known during the CMake configuration stage.
.. note::
This is where generator expressions get their name, they are evaluated when
the underlying build system is being generated.
Generator expressions were commonly used in combination with
:command:`target_include_directories` to express include directory requirements
across the build and install tree, but file sets have superseded this use case.
Their most common applications now are in multi-config generators and
intricate dependency injection systems.
.. code-block:: cmake
target_compile_definitions(MyApp PRIVATE "MYAPP_BUILD_CONFIG=$<CONFIG>")
Goal
----
Add a generator expression to ``SimpleTest`` that checks the build configuration
inside a compile definition.
Helpful Resources
-----------------
* :command:`target_compile_definitions`
* :manual:`cmake-generator-expressions(7)`
Files to Edit
-------------
* ``SimpleTest/CMakeLists.txt``
Getting Started
---------------
For this step we will only be editing the ``SimpleTest`` project in the
``Step11`` folder. Complete ``TODO 2``.
Build and Run
-------------
To build the project we first need configure and install ``SimpleTest``.
Navigate to ``Help/guide/Step11/SimpleTest`` and run the appropriate commands.
.. code-block:: console
cmake --preset tutorial
cmake --install build
Then navigate to ``Help/guide/Step11/TutorialProject`` and perform the usual build.
.. code-block:: console
cmake --preset tutorial
cmake --build build
When running the ``TestMathFunctions`` binary directly, we should a message
naming the build configuration used to build the executable (not necessarily the
same as configuration used to configure ``SimpleTest``). On single configuration
generators, the build configuration can be changed by setting
:variable:`CMAKE_BUILD_TYPE`.
Solution
--------
We add a single line to the ``SimpleTest`` CML.
.. raw:: html
<details><summary>TODO 2 Click to show/hide answer</summary>
.. literalinclude:: Complete/SimpleTest/CMakeLists.txt
:caption: TODO 2: SimpleTest/CMakeLists.txt
:name: SimpleTest/CMakeLists.txt-target_compile_definitions
:language: cmake
:start-at: target_compile_definitions
:end-at: target_compile_definitions
.. raw:: html
</details>

View File

@@ -1,86 +0,0 @@
Step 12: Packaging Debug and Release
====================================
**Note:** This example is valid for single-configuration generators and will
not work for multi-configuration generators (e.g. Visual Studio).
By default, CMake's model is that a build directory only contains a single
configuration, be it Debug, Release, MinSizeRel, or RelWithDebInfo. It is
possible, however, to setup CPack to bundle multiple build directories and
construct a package that contains multiple configurations of the same project.
First, we want to ensure that the debug and release builds use different names
for the libraries that will be installed. Let's use ``d`` as the
postfix for the debug libraries.
Set :variable:`CMAKE_DEBUG_POSTFIX` near the beginning of the top-level
``CMakeLists.txt`` file:
.. literalinclude:: Complete/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-CMAKE_DEBUG_POSTFIX-variable
:language: cmake
:start-after: project(Tutorial VERSION 1.0)
:end-before: target_compile_features(tutorial_compiler_flags
And the :prop_tgt:`DEBUG_POSTFIX` property on the tutorial executable:
.. literalinclude:: Complete/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-DEBUG_POSTFIX-property
:language: cmake
:start-after: # add the executable
:end-before: # add the binary tree to the search path for include files
Let's also add version numbering to the ``MathFunctions`` library. In
``MathFunctions/CMakeLists.txt``, set the :prop_tgt:`VERSION` and
:prop_tgt:`SOVERSION` properties:
.. literalinclude:: Complete/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-VERSION-properties
:language: cmake
:start-after: # setup the version numbering
:end-before: # install libs
From the ``Step12`` directory, create ``debug`` and ``release``
subdirectories. The layout will look like:
.. code-block:: none
- Step12
- debug
- release
Now we need to setup debug and release builds. We can use
:variable:`CMAKE_BUILD_TYPE` to set the configuration type:
.. code-block:: console
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
Now that both the debug and release builds are complete, we can use a custom
configuration file to package both builds into a single release. In the
``Step12`` directory, create a file called ``MultiCPackConfig.cmake``. In this
file, first include the default configuration file that was created by the
:manual:`cmake <cmake(1)>` executable.
Next, use the ``CPACK_INSTALL_CMAKE_PROJECTS`` variable to specify which
projects to install. In this case, we want to install both debug and release.
.. literalinclude:: Complete/MultiCPackConfig.cmake
:caption: MultiCPackConfig.cmake
:name: MultiCPackConfig.cmake
:language: cmake
From the ``Step12`` directory, run :manual:`cpack <cpack(1)>` specifying our
custom configuration file with the ``config`` option:
.. code-block:: console
cpack --config MultiCPackConfig.cmake

View File

@@ -1,64 +0,0 @@
Step 9: Packaging an Installer
==============================
Next suppose that we want to distribute our project to other people so that
they can use it. We want to provide both binary and source distributions on a
variety of platforms. This is a little different from the install we did
previously in :guide:`tutorial/Installing and Testing`, where we were
installing the binaries that we had built from the source code. In this
example we will be building installation packages that support binary
installations and package management features. To accomplish this we will use
CPack to create platform specific installers. Specifically we need to add a
few lines to the bottom of our top-level ``CMakeLists.txt`` file.
.. literalinclude:: Step10/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-include-CPack
:language: cmake
:start-after: # setup installer
That is all there is to it. We start by including
:module:`InstallRequiredSystemLibraries`. This module will include any runtime
libraries that are needed by the project for the current platform. Next we set
some CPack variables to where we have stored the license and version
information for this project. The version information was set earlier in this
tutorial and the ``License.txt`` has been included in the top-level source
directory for this step. The :variable:`CPACK_GENERATOR` and
:variable:`CPACK_SOURCE_GENERATOR` variables select the generators used for
binary and source installations, respectively.
Finally we include the :module:`CPack module <CPack>` which will use these
variables and some other properties of the current system to setup an
installer.
The next step is to build the project in the usual manner and then run the
:manual:`cpack <cpack(1)>` executable. To build a binary distribution, from the
binary directory run:
.. code-block:: console
cpack
To specify the binary generator, use the :option:`-G <cpack -G>` option. For
multi-config builds, use :option:`-C <cpack -C>` to specify the configuration.
For example:
.. code-block:: console
cpack -G ZIP -C Debug
For a list of available generators, see :manual:`cpack-generators(7)` or call
:option:`cpack --help`. An :cpack_gen:`archive generator <CPack Archive Generator>`
like ZIP creates a compressed archive of all *installed* files.
To create an archive of the *full* source tree you would type:
.. code-block:: console
cpack --config CPackSourceConfig.cmake
Alternatively, run ``make package`` or right click the ``Package`` target and
``Build Project`` from an IDE.
Run the installer found in the binary directory. Then run the installed
executable and verify that it works.

View File

@@ -1,61 +0,0 @@
Step 10: Selecting Static or Shared Libraries
=============================================
In this section we will show how the :variable:`BUILD_SHARED_LIBS` variable can
be used to control the default behavior of :command:`add_library`,
and allow control over how libraries without an explicit type (``STATIC``,
``SHARED``, ``MODULE`` or ``OBJECT``) are built.
To accomplish this we need to add :variable:`BUILD_SHARED_LIBS` to the
top-level ``CMakeLists.txt``. We use the :command:`option` command as it allows
users to optionally select if the value should be ``ON`` or ``OFF``.
.. literalinclude:: Step11/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-option-BUILD_SHARED_LIBS
:language: cmake
:start-after: set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
:end-before: # configure a header file to pass the version number only
Next, we need to specify output directories for our static and shared
libraries.
.. literalinclude:: Step11/CMakeLists.txt
:caption: CMakeLists.txt
:name: CMakeLists.txt-cmake-output-directories
:language: cmake
:start-after: # we don't need to tinker with the path to run the executable
:end-before: # configure a header file to pass the version number only
Finally, update ``MathFunctions/MathFunctions.h`` to use dll export defines:
.. literalinclude:: Step11/MathFunctions/MathFunctions.h
:caption: MathFunctions/MathFunctions.h
:name: MathFunctions/MathFunctions.h
:language: c++
At this point, if you build everything, you may notice that linking fails
as we are combining a static library without position independent code with a
library that has position independent code. The solution to this is to
explicitly set the :prop_tgt:`POSITION_INDEPENDENT_CODE` target property of
SqrtLibrary to be ``True`` when building shared libraries.
.. literalinclude:: Step11/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-POSITION_INDEPENDENT_CODE
:language: cmake
:start-at: # state that SqrtLibrary need PIC when the default is shared libraries
:end-at: )
Define ``EXPORTING_MYMATH`` stating we are using ``declspec(dllexport)`` when
building on Windows.
.. literalinclude:: Step11/MathFunctions/CMakeLists.txt
:caption: MathFunctions/CMakeLists.txt
:name: MathFunctions/CMakeLists.txt-dll-export
:language: cmake
:start-at: # define the symbol stating we are using the declspec(dllexport) when
:end-at: target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
**Exercise**: We modified ``MathFunctions.h`` to use dll export defines.
Using CMake documentation can you find a helper module to simplify this?

View File

@@ -1,16 +1,21 @@
# TODO 1: Set the minimum required version of CMake to be 3.10
# TODO1: Set the minimum required version of CMake to be 3.23
# TODO 2: Create a project named Tutorial
# TODO2: Create a project named Tutorial
# TODO 7: Set the project version number as 1.0 in the above project command
# TODO3: Add an executable target called Tutorial to the project
# TODO 6: Set the variable CMAKE_CXX_STANDARD to 11
# and the variable CMAKE_CXX_STANDARD_REQUIRED to True
# TODO4: Add the Tutorial/Tutorial.cxx source file to the Tutorial target
# TODO 8: Use configure_file to configure and copy TutorialConfig.h.in to
# TutorialConfig.h
# TODO7: Add the MathFunctions library as a linked dependency
# to the Tutorial target
# TODO 3: Add an executable called Tutorial to the project
# Hint: Be sure to specify the source file as tutorial.cxx
# TODO11: Add the Tutorial subdirectory to the project
# TODO 9: Use target_include_directories to include ${PROJECT_BINARY_DIR}
# TODO5: Add a library target called MathFunctions to the project
# TODO6: Add the source and header file located in Step1/MathFunctions to the
# MathFunctions target, note that the intended way to include the
# MathFunctions header is:
# #include <MathFunctions.h>
# TODO13: Add the MathFunctions subdirectory to the project

View File

@@ -0,0 +1,2 @@
# TODO12: Move all the MathFunctions target commands to this CMakeLists.txt.
# Ensure that all paths are updated to be relative to this new location.

View File

@@ -1,9 +1,6 @@
#include "mysqrt.h"
#include <iostream>
namespace mathfunctions {
namespace detail {
namespace {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
@@ -25,4 +22,10 @@ double mysqrt(double x)
return result;
}
}
namespace mathfunctions {
double sqrt(double x)
{
return mysqrt(x);
}
}

View File

@@ -0,0 +1,2 @@
# TODO10: Move all the Tutorial target commands to this CMakeLists.txt. Ensure
# that all paths are updated to be relative to this new location.

View File

@@ -3,15 +3,11 @@
#include <iostream>
#include <string>
#include "MathFunctions.h"
#include "TutorialConfig.h"
// TODO8: Include the MathFunctions header
int main(int argc, char* argv[])
{
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
@@ -19,9 +15,9 @@ int main(int argc, char* argv[])
// convert input to double
double const inputValue = std::stod(argv[1]);
double const outputValue = mathfunctions::sqrt(inputValue);
// TODO9: Use the mathfunctions::sqrt function
// calculate square root
double const outputValue = std::sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

View File

@@ -1,2 +0,0 @@
// the configured options and settings for Tutorial
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR

View File

@@ -1,27 +0,0 @@
// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib> // TODO 5: Remove this line
#include <iostream>
#include <string>
// TODO 11: Include TutorialConfig.h
int main(int argc, char* argv[])
{
if (argc < 2) {
// TODO 12: Create a print statement using Tutorial_VERSION_MAJOR
// and Tutorial_VERSION_MINOR
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
// convert input to double
// TODO 4: Replace atof(argv[1]) with std::stod(argv[1])
double const inputValue = atof(argv[1]);
// calculate square root
double const outputValue = sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

View File

@@ -1,77 +0,0 @@
cmake_minimum_required(VERSION 3.15)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# add compiler warning flags just when building this project via
# the BUILD_INTERFACE genex
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
# add the install targets
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
# enable testing
include(CTest)
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction()
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
# setup installer
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_GENERATOR "TGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")
include(CPack)

View File

@@ -1,3 +0,0 @@
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
set(CTEST_SUBMIT_URL "https://my.cdash.org/submit.php?project=CMakeTutorial")

View File

@@ -1,2 +0,0 @@
This is the open source License.txt file introduced in
CMake/Tutorial/Step9...

View File

@@ -1,45 +0,0 @@
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
include(MakeTable.cmake) # generates Table.h
# library that just does sqrt
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# state that we depend on our binary dir to find Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
# link SqrtLibrary to tutorial_compiler_flags
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# link MathFunctions to tutorial_compiler_flags
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
# install libs
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
# install include headers
install(FILES MathFunctions.h DESTINATION include)

View File

@@ -1,10 +0,0 @@
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
# add the command to generate the source code
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)

View File

@@ -1,20 +0,0 @@
#include "MathFunctions.h"
#include <cmath>
#ifdef USE_MYMATH
# include "mysqrt.h"
#endif
namespace mathfunctions {
double sqrt(double x)
{
// which square root function should we use?
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}

View File

@@ -1,3 +0,0 @@
namespace mathfunctions {
double sqrt(double x);
}

View File

@@ -1,37 +0,0 @@
#include <iostream>
#include "MathFunctions.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}

View File

@@ -1,6 +0,0 @@
namespace mathfunctions {
namespace detail {
double mysqrt(double x);
}
}

View File

@@ -0,0 +1,52 @@
# A very simple test framework for demonstrating how dependencies work
cmake_minimum_required(VERSION 3.23)
project(SimpleTest
VERSION 0.0.1
)
add_library(SimpleTest INTERFACE)
target_sources(SimpleTest
INTERFACE
FILE_SET HEADERS
FILES
SimpleTest.h
)
target_compile_features(SimpleTest INTERFACE cxx_std_20)
# TODO6: Find the TransitiveDep package with find_package. The SimpleTest
# build should fail if TransitiveDep cannot be found.
# TODO7: Add the TransitiveDep::TransitiveDep target to the SimpleTest interface
# library's links. Remember that interface libraries can only have
# interface properties.
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
install(
TARGETS SimpleTest
EXPORT SimpleTestTargets
FILE_SET HEADERS
)
install(
EXPORT SimpleTestTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SimpleTest
NAMESPACE SimpleTest::
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/SimpleTestConfigVersion.cmake
COMPATIBILITY ExactVersion
ARCH_INDEPENDENT
)
install(
FILES
cmake/simpletest_discover_impl.cmake
cmake/simpletest_discover_tests.cmake
cmake/SimpleTestConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/SimpleTestConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SimpleTest
)

View File

@@ -0,0 +1,16 @@
{
"version": 4,
"configurePresets": [
{
"name": "tutorial",
"displayName": "SimpleTest Preset",
"description": "Preset to use with the tutorial's SimpleTest library",
"binaryDir": "${sourceDir}/build",
"installDir": "${sourceParentDir}/install",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "20",
"CMAKE_PREFIX_PATH": "${sourceParentDir}/install"
}
}
]
}

View File

@@ -0,0 +1,151 @@
#pragma once
#include <cstdio>
#include <map>
#include <string_view>
namespace SimpleTest {
using TestFunc = void (*)();
using Registry = std::map<std::string_view, TestFunc, std::less<>>;
inline Registry g_registry;
inline Registry& registry()
{
return g_registry;
}
struct failure
{
char const* file;
int line;
char const* expr;
};
struct Registrar
{
template <std::size_t N>
Registrar(char const (&name)[N], TestFunc f)
{
auto [it, inserted] =
registry().emplace(std::string_view{ name, N ? (N - 1) : 0 }, f);
if (!inserted) {
std::printf("[ WARN ] duplicate test name: %.*s\n",
int(it->first.size()), it->first.data());
}
}
};
inline Registry const& all()
{
return registry();
}
inline TestFunc find(std::string_view name)
{
auto it = registry().find(name);
return it == registry().end() ? nullptr : it->second;
}
}
#define SIMPLETEST_CONCAT_(a, b) a##b
#define SIMPLETEST_CONCAT(a, b) SIMPLETEST_CONCAT_(a, b)
#define TEST(name_literal) \
static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)(); \
static ::SimpleTest::Registrar SIMPLETEST_CONCAT(_simpletest_reg_, \
__LINE__)( \
name_literal, &SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)); \
static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)()
// Minimal assertion
#define REQUIRE(expr) \
do { \
if (!(expr)) \
throw ::SimpleTest::failure{ __FILE__, __LINE__, #expr }; \
} while (0)
int main(int argc, char** argv)
{
using namespace ::SimpleTest;
std::string_view arg1 =
(argc >= 2) ? std::string_view{ argv[1] } : std::string_view{};
if (arg1 == "--list") {
bool first = true;
for (auto const& [name, _] : registry()) {
if (!first)
std::printf(",");
std::printf("%.*s", int(name.size()), name.data());
first = false;
}
std::printf("\n");
return 0;
}
if (arg1 == "--test") {
if (argc < 3) {
std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
return 2;
}
#ifdef SIMPLETEST_CONFIG
std::printf("SimpleTest built with config: %s\n", SIMPLETEST_CONFIG);
#endif
std::string_view name{ argv[2] };
auto it = registry().find(name);
if (it == registry().end()) {
std::printf("[ NOTFOUND ] %s\n", argv[2]);
return 2;
}
int failed = 0;
std::printf("[ RUN ] %.*s\n", int(it->first.size()),
it->first.data());
try {
it->second();
std::printf("[ OK] %.*s\n", int(it->first.size()),
it->first.data());
} catch (failure const& f) {
std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(it->first.size()),
it->first.data(), f.file, f.line, f.expr);
failed = 1;
} catch (...) {
std::printf("[ FAILED ] %.*s : unknown exception\n",
int(it->first.size()), it->first.data());
failed = 1;
}
return failed;
}
if (argc > 1) {
std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
return 2;
}
#ifdef SIMPLETEST_CONFIG
std::printf("SimpleTest built with config: %s\n", SIMPLETEST_CONFIG);
#endif
// Default: run all tests.
int failed = 0;
for (auto const& [name, func] : all()) {
std::printf("[ RUN ] %.*s\n", int(name.size()), name.data());
try {
func();
std::printf("[ OK ] %.*s\n", int(name.size()), name.data());
} catch (failure const& f) {
std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(name.size()),
name.data(), f.file, f.line, f.expr);
failed = 1;
} catch (...) {
std::printf("[ FAILED ] %.*s : unknown exception\n", int(name.size()),
name.data());
failed = 1;
}
}
return failed;
}

View File

@@ -0,0 +1,6 @@
# TODO8: Include the CMakeFindDependencyMacro and use find_dependency to find
# the TransitiveDep package.
include(${CMAKE_CURRENT_LIST_DIR}/SimpleTestTargets.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/simpletest_discover_tests.cmake)

View File

@@ -0,0 +1,32 @@
if(NOT DEFINED TEST_EXE OR NOT DEFINED OUT_FILE)
# noqa: spellcheck off
message(FATAL_ERROR "simpletest_discover: need -DTEST_EXE and -DOUT_FILE")
# noqa: spellcheck on
endif()
execute_process(
COMMAND ${TEST_EXE} --list
RESULT_VARIABLE _rc
OUTPUT_VARIABLE _out
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _rc EQUAL 0)
file(WRITE ${OUT_FILE} "# simpletest: --list failed (rc=${_rc})\n")
message(FATAL_ERROR "simpletest_discover: '${TEST_EXE} --list' failed (${_rc})\n${_err}")
endif()
if(_out STREQUAL "")
file(WRITE ${OUT_FILE} "# simpletest: no tests\n")
return()
endif()
string(REPLACE "," ";" _names "${_out}")
file(WRITE ${OUT_FILE} "# Auto-generated by simpletest_discover_impl.cmake\n")
foreach(_name IN LISTS _names)
file(APPEND ${OUT_FILE}
"add_test([=[${_name}]=] \"${TEST_EXE}\" \"--test\" \"${_name}\")\n"
)
endforeach()

View File

@@ -0,0 +1,27 @@
set(_simpletest_impl_script ${CMAKE_CURRENT_LIST_DIR}/simpletest_discover_impl.cmake)
function(simpletest_discover_tests target)
if(NOT TARGET ${target})
message(FATAL_ERROR "simpletest_discover_tests: no such target '${target}'")
endif()
set(_out ${CMAKE_CURRENT_BINARY_DIR}/${target}_ctests.cmake)
if(NOT EXISTS ${_out})
file(WRITE ${_out} "# Populated after building ${target}\n")
endif()
# noqa: spellcheck off
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DTEST_EXE=$<TARGET_FILE:${target}>
-DOUT_FILE=${_out}
-P ${_simpletest_impl_script}
BYPRODUCTS ${_out}
COMMENT "SimpleTest: Discovering tests in ${target}"
VERBATIM
)
# noqa: spellcheck on
set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES ${_out})
endfunction()

View File

@@ -1,3 +0,0 @@
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

View File

@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.23)
project(Tutorial
VERSION 1.0.0
)
option(TUTORIAL_BUILD_UTILITIES "Build the Tutorial executable" ON)
option(TUTORIAL_USE_STD_SQRT "Use std::sqrt" OFF)
option(TUTORIAL_ENABLE_IPO "Check for and use IPO support" ON)
option(BUILD_TESTING "Enable testing and build tests" ON)
if(TUTORIAL_ENABLE_IPO)
include(CheckIPOSupported)
check_ipo_supported(RESULT result OUTPUT output)
if(result)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
else()
message(WARNING "IPO is not supported ${message}")
endif()
endif()
if(TUTORIAL_BUILD_UTILITIES)
add_subdirectory(Tutorial)
install(
TARGETS Tutorial
EXPORT TutorialTargets
)
endif()
if(BUILD_TESTING)
enable_testing()
add_subdirectory(Tests)
endif()
add_subdirectory(MathFunctions)
include(GNUInstallDirs)
install(
TARGETS MathFunctions OpAdd OpMul OpSub MathLogger SqrtTable
EXPORT TutorialTargets
FILE_SET HEADERS
)
install(
EXPORT TutorialTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
NAMESPACE Tutorial::
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
COMPATIBILITY ExactVersion
)
install(
FILES
cmake/TutorialConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)

View File

@@ -0,0 +1,16 @@
{
"version": 4,
"configurePresets": [
{
"name": "tutorial",
"displayName": "Tutorial Preset",
"description": "Preset to use with the tutorial",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"TODO4": "Add ${sourceParentDir}/install to CMAKE_PREFIX_PATH",
"TUTORIAL_USE_STD_SQRT": "OFF",
"TUTORIAL_ENABLE_IPO": "OFF"
}
}
]
}

View File

@@ -0,0 +1,54 @@
add_library(MathFunctions)
target_sources(MathFunctions
PRIVATE
MathFunctions.cxx
PUBLIC
FILE_SET HEADERS
FILES
MathFunctions.h
)
target_link_libraries(MathFunctions
PRIVATE
MathLogger
SqrtTable
PUBLIC
OpAdd
OpMul
OpSub
)
target_compile_features(MathFunctions PRIVATE cxx_std_20)
if(TUTORIAL_USE_STD_SQRT)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_STD_SQRT)
endif()
include(CheckIncludeFiles)
check_include_files(emmintrin.h HAS_EMMINTRIN LANGUAGE CXX)
if(HAS_EMMINTRIN)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_SSE2)
endif()
include(CheckSourceCompiles)
check_source_compiles(CXX
[=[
typedef double v2df __attribute__((vector_size(16)));
int main() {
__builtin_ia32_sqrtsd(v2df{});
}
]=]
HAS_GNU_BUILTIN
)
if(HAS_GNU_BUILTIN)
target_compile_definitions(MathFunctions PRIVATE TUTORIAL_USE_GNU_BUILTIN)
endif()
add_subdirectory(MathLogger)
add_subdirectory(MathExtensions)
add_subdirectory(MakeTable)

View File

@@ -0,0 +1,28 @@
add_executable(MakeTable)
target_sources(MakeTable
PRIVATE
MakeTable.cxx
)
add_custom_command(
OUTPUT SqrtTable.h
COMMAND MakeTable SqrtTable.h
DEPENDS MakeTable
VERBATIM
)
add_custom_target(RunMakeTable DEPENDS SqrtTable.h)
add_library(SqrtTable INTERFACE)
target_sources(SqrtTable
INTERFACE
FILE_SET HEADERS
BASE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
FILES
${CMAKE_CURRENT_BINARY_DIR}/SqrtTable.h
)
add_dependencies(SqrtTable RunMakeTable)

View File

@@ -0,0 +1,3 @@
add_subdirectory(OpAdd)
add_subdirectory(OpMul)
add_subdirectory(OpSub)

View File

@@ -0,0 +1,11 @@
add_library(OpAdd OBJECT)
target_sources(OpAdd
PRIVATE
OpAdd.cxx
INTERFACE
FILE_SET HEADERS
FILES
OpAdd.h
)

View File

@@ -0,0 +1,6 @@
namespace mathfunctions {
double OpAdd(double a, double b)
{
return a + b;
}
}

Some files were not shown because too many files have changed in this diff Show More