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