mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-01 11:22:21 -06:00
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:
committed by
Brad King
parent
9e89400d13
commit
b2e3e3e30e
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
583
Help/guide/tutorial/CMake Language Fundamentals.rst
Normal file
583
Help/guide/tutorial/CMake Language Fundamentals.rst
Normal 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>
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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")
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
|
||||
@@ -1,2 +0,0 @@
|
||||
This is the open source License.txt file introduced in
|
||||
CMake/Tutorial/Step9...
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
namespace mathfunctions {
|
||||
namespace detail {
|
||||
double mysqrt(double x);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
include("release/CPackConfig.cmake")
|
||||
|
||||
set(CPACK_INSTALL_CMAKE_PROJECTS
|
||||
"debug;Tutorial;ALL;/"
|
||||
"release;Tutorial;ALL;/"
|
||||
)
|
||||
53
Help/guide/tutorial/Complete/SimpleTest/CMakeLists.txt
Normal file
53
Help/guide/tutorial/Complete/SimpleTest/CMakeLists.txt
Normal 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
|
||||
)
|
||||
16
Help/guide/tutorial/Complete/SimpleTest/CMakePresets.json
Normal file
16
Help/guide/tutorial/Complete/SimpleTest/CMakePresets.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
155
Help/guide/tutorial/Complete/SimpleTest/SimpleTest.h
Normal file
155
Help/guide/tutorial/Complete/SimpleTest/SimpleTest.h
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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@
|
||||
59
Help/guide/tutorial/Complete/TutorialProject/CMakeLists.txt
Normal file
59
Help/guide/tutorial/Complete/TutorialProject/CMakeLists.txt
Normal 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
|
||||
)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
add_subdirectory(OpAdd)
|
||||
add_subdirectory(OpMul)
|
||||
add_subdirectory(OpSub)
|
||||
@@ -0,0 +1,11 @@
|
||||
add_library(OpAdd OBJECT)
|
||||
|
||||
target_sources(OpAdd
|
||||
PRIVATE
|
||||
OpAdd.cxx
|
||||
|
||||
INTERFACE
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
OpAdd.h
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mathfunctions {
|
||||
double OpAdd(double a, double b)
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace mathfunctions {
|
||||
double OpAdd(double a, double b);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
add_library(OpMul OBJECT)
|
||||
|
||||
target_sources(OpMul
|
||||
PRIVATE
|
||||
OpMul.cxx
|
||||
|
||||
INTERFACE
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
OpMul.h
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mathfunctions {
|
||||
double OpMul(double a, double b)
|
||||
{
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace mathfunctions {
|
||||
double OpMul(double a, double b);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
add_library(OpSub OBJECT)
|
||||
|
||||
target_sources(OpSub
|
||||
PRIVATE
|
||||
OpSub.cxx
|
||||
|
||||
INTERFACE
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
OpSub.h
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mathfunctions {
|
||||
double OpSub(double a, double b)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace mathfunctions {
|
||||
double OpSub(double a, double b);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <OpAdd.h>
|
||||
#include <OpMul.h>
|
||||
#include <OpSub.h>
|
||||
|
||||
namespace mathfunctions {
|
||||
double sqrt(double x);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
add_library(MathLogger INTERFACE)
|
||||
|
||||
target_sources(MathLogger
|
||||
INTERFACE
|
||||
FILE_SET HEADERS
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace mathlogger {
|
||||
inline void WriteLog(std::string const& msg)
|
||||
{
|
||||
std::cout << msg;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/TutorialTargets.cmake)
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define UNPACKAGED_HEADER_FOUND
|
||||
@@ -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)
|
||||
606
Help/guide/tutorial/Configuration and Cache Variables.rst
Normal file
606
Help/guide/tutorial/Configuration and Cache Variables.rst
Normal 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>
|
||||
279
Help/guide/tutorial/Custom Commands and Generated Files.rst
Normal file
279
Help/guide/tutorial/Custom Commands and Generated Files.rst
Normal 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>
|
||||
529
Help/guide/tutorial/Finding Dependencies.rst
Normal file
529
Help/guide/tutorial/Finding Dependencies.rst
Normal 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>
|
||||
805
Help/guide/tutorial/Getting Started with CMake.rst
Normal file
805
Help/guide/tutorial/Getting Started with CMake.rst
Normal 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>
|
||||
446
Help/guide/tutorial/In-Depth CMake Library Concepts.rst
Normal file
446
Help/guide/tutorial/In-Depth CMake Library Concepts.rst
Normal 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>
|
||||
532
Help/guide/tutorial/In-Depth CMake Target Commands.rst
Normal file
532
Help/guide/tutorial/In-Depth CMake Target Commands.rst
Normal 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>
|
||||
418
Help/guide/tutorial/In-Depth System Introspection.rst
Normal file
418
Help/guide/tutorial/In-Depth System Introspection.rst
Normal 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.
|
||||
596
Help/guide/tutorial/Installation Commands and Concepts.rst
Normal file
596
Help/guide/tutorial/Installation Commands and Concepts.rst
Normal 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>
|
||||
@@ -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>
|
||||
188
Help/guide/tutorial/Miscellaneous Features.rst
Normal file
188
Help/guide/tutorial/Miscellaneous Features.rst
Normal 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>
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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?
|
||||
@@ -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
|
||||
|
||||
2
Help/guide/tutorial/Step1/MathFunctions/CMakeLists.txt
Normal file
2
Help/guide/tutorial/Step1/MathFunctions/CMakeLists.txt
Normal 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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
2
Help/guide/tutorial/Step1/Tutorial/CMakeLists.txt
Normal file
2
Help/guide/tutorial/Step1/Tutorial/CMakeLists.txt
Normal 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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// the configured options and settings for Tutorial
|
||||
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -1,2 +0,0 @@
|
||||
This is the open source License.txt file introduced in
|
||||
CMake/Tutorial/Step9...
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace mathfunctions {
|
||||
double sqrt(double x);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
namespace mathfunctions {
|
||||
namespace detail {
|
||||
double mysqrt(double x);
|
||||
}
|
||||
}
|
||||
52
Help/guide/tutorial/Step10/SimpleTest/CMakeLists.txt
Normal file
52
Help/guide/tutorial/Step10/SimpleTest/CMakeLists.txt
Normal 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
|
||||
)
|
||||
16
Help/guide/tutorial/Step10/SimpleTest/CMakePresets.json
Normal file
16
Help/guide/tutorial/Step10/SimpleTest/CMakePresets.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
151
Help/guide/tutorial/Step10/SimpleTest/SimpleTest.h
Normal file
151
Help/guide/tutorial/Step10/SimpleTest/SimpleTest.h
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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@
|
||||
63
Help/guide/tutorial/Step10/TutorialProject/CMakeLists.txt
Normal file
63
Help/guide/tutorial/Step10/TutorialProject/CMakeLists.txt
Normal 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
|
||||
)
|
||||
16
Help/guide/tutorial/Step10/TutorialProject/CMakePresets.json
Normal file
16
Help/guide/tutorial/Step10/TutorialProject/CMakePresets.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
add_subdirectory(OpAdd)
|
||||
add_subdirectory(OpMul)
|
||||
add_subdirectory(OpSub)
|
||||
@@ -0,0 +1,11 @@
|
||||
add_library(OpAdd OBJECT)
|
||||
|
||||
target_sources(OpAdd
|
||||
PRIVATE
|
||||
OpAdd.cxx
|
||||
|
||||
INTERFACE
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
OpAdd.h
|
||||
)
|
||||
@@ -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
Reference in New Issue
Block a user