123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- 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_LIBS
- 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>
|