Step 10: Selecting Static or Shared Libraries — CMake 3.26.4 Documentation

# Step 10: Selecting Static or Shared Libraries

In this section we will show how the `BUILD_SHARED_LIBS` variable can be used to control the default behavior of `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 `BUILD_SHARED_LIBS` to the top-level `CMakeLists.txt`. We use the `option()` command as it allows users to optionally select if the value should be `ON` or `OFF`.

Next we are going to refactor `MathFunctions` to become a real library that encapsulates using `mysqrt` or `sqrt`, instead of requiring the calling code to do this logic. This will also mean that `USE_MYMATH` will not control building `MathFunctions`, but instead will control the behavior of this library.

The first step is to update the starting section of the top-level `CMakeLists.txt` to look like:

CMakeLists.txt

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

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

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

Now that we have made `MathFunctions` always be used, we will need to update the logic of that library. So, in `MathFunctions/CMakeLists.txt` we need to create a SqrtLibrary that will conditionally be built and installed when `USE_MYMATH` is enabled. Now, since this is a tutorial, we are going to explicitly require that SqrtLibrary is built statically.

The end result is that `MathFunctions/CMakeLists.txt` should look like:

MathFunctions/CMakeLists.txt

    # 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")

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

      # 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}
                                 )

      target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
      target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
    endif()

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

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

Next, update `MathFunctions/mysqrt.cxx` to use the `mathfunctions` and `detail` namespaces:

MathFunctions/mysqrt.cxx

    #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;
    }
    }
    }

We also need to make some changes in `tutorial.cxx`, so that it no longer uses `USE_MYMATH`:

1. Always include `MathFunctions.h`

2. Always use `mathfunctions::sqrt`

3. Don't include `cmath`

Finally, update `MathFunctions/MathFunctions.h` to use dll export defines:

MathFunctions/MathFunctions.h

    #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);
    }

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 `POSITION_INDEPENDENT_CODE` target property of SqrtLibrary to be `True` when building shared libraries.

MathFunctions/CMakeLists.txt

      # state that SqrtLibrary need PIC when the default is shared libraries
      set_target_properties(SqrtLibrary PROPERTIES
                            POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
                            )

Exercise: We modified `MathFunctions.h` to use dll export defines. Using CMake documentation can you find a helper module to simplify this?