Installing FetchContent targets in CMake

The FetchContent module is the easiest and most efficient way of adding dependencies to your CMake project. In contrast to ExternalProject, it fetches the dependency at the configuration time, which makes it easier to discover imported targets and verify that you are linking your executables and libraries correctly. Thus, you can now easily add to your C/C++ project dependencies that you do not expect to be available on the user’s system - libraries, SDKs, and internal tools such as testing frameworks. In fact, the first example shown in CMake documentation uses the gtest framework:

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
FetchContent_MakeAvailable(googletest)

And it works great! We don’t have to do anything else, as CMake will fetch the gtest release, configure it, and build at runtime. We only have to link our test targets against imported gtest targets:

target_link_libraries(${target} PRIVATE GTest::gtest_main)
target_link_libraries(${target} PRIVATE GTest::gmock_main)

gtest_discover_tests(${target})

However, this solution has one drawback that we might only realize very late in the process. The issue is clearly visible in the log before:

-- Installing: /install_process/include/gmock
-- Installing: /install_process/include/gmock/gmock-more-matchers.h
-- Installing: /install_process/include/gmock/internal
-- Installing: /install_process/include/gmock/internal/custom
-- Installing: /install_process/include/gmock/internal/custom/gmock-matchers.h
-- Installing: /install_process/include/gmock/internal/custom/gmock-generated-actions.h
-- Installing: /install_process/include/gmock/internal/custom/gmock-port.h
-- Installing: /install_process/include/gmock/internal/custom/README.md
-- Installing: /install_process/include/gmock/internal/gmock-pp.h
-- Installing: /install_process/include/gmock/internal/gmock-port.h
-- Installing: /install_process/include/gmock/internal/gmock-internal-utils.h
-- Installing: /install_process/include/gmock/gmock-function-mocker.h
-- Installing: /install_process/include/gmock/gmock-spec-builders.h
-- Installing: /install_process/include/gmock/gmock.h
-- Installing: /install_process/include/gmock/gmock-matchers.h
-- Installing: /install_process/include/gmock/gmock-nice-strict.h
-- Installing: /install_process/include/gmock/gmock-cardinalities.h
-- Installing: /install_process/include/gmock/gmock-actions.h
-- Installing: /install_process/include/gmock/gmock-more-actions.h
-- Installing: /install_process/lib/libgmock.a
-- Installing: /install_process/lib/libgmock_main.a
-- Installing: /install_process/lib/pkgconfig/gmock.pc
-- Installing: /install_process/lib/pkgconfig/gmock_main.pc
-- Installing: /install_process/lib/cmake/GTest/GTestTargets.cmake
-- Installing: /install_process/lib/cmake/GTest/GTestTargets-relwithdebinfo.cmake
-- Installing: /install_process/lib/cmake/GTest/GTestConfigVersion.cmake
-- Installing: /install_process/lib/cmake/GTest/GTestConfig.cmake
-- Up-to-date: /install_process/include
-- Installing: /install_process/include/gtest
-- Installing: /install_process/include/gtest/internal
-- Installing: /install_process/include/gtest/internal/gtest-port-arch.h
-- Installing: /install_process/include/gtest/internal/custom
-- Installing: /install_process/include/gtest/internal/custom/gtest-port.h
-- Installing: /install_process/include/gtest/internal/custom/gtest-printers.h
-- Installing: /install_process/include/gtest/internal/custom/README.md
-- Installing: /install_process/include/gtest/internal/custom/gtest.h
-- Installing: /install_process/include/gtest/internal/gtest-death-test-internal.h
-- Installing: /install_process/include/gtest/internal/gtest-internal.h
-- Installing: /install_process/include/gtest/internal/gtest-port.h
-- Installing: /install_process/include/gtest/internal/gtest-filepath.h
-- Installing: /install_process/include/gtest/internal/gtest-param-util.h
-- Installing: /install_process/include/gtest/internal/gtest-type-util.h
-- Installing: /install_process/include/gtest/internal/gtest-string.h
-- Installing: /install_process/include/gtest/gtest-spi.h
-- Installing: /install_process/include/gtest/gtest-typed-test.h
-- Installing: /install_process/include/gtest/gtest-matchers.h
-- Installing: /install_process/include/gtest/gtest-test-part.h
-- Installing: /install_process/include/gtest/gtest-death-test.h
-- Installing: /install_process/include/gtest/gtest-message.h
-- Installing: /install_process/include/gtest/gtest_prod.h
-- Installing: /install_process/include/gtest/gtest-printers.h
-- Installing: /install_process/include/gtest/gtest_pred_impl.h
-- Installing: /install_process/include/gtest/gtest-param-test.h
-- Installing: /install_process/include/gtest/gtest.h
-- Installing: /install_process/lib/libgtest.a
-- Installing: /install_process/lib/libgtest_main.a
-- Installing: /install_process/lib/pkgconfig/gtest.pc
-- Installing: /install_process/lib/pkgconfig/gtest_main.pc

CMake automatically installed targets imported in the gtest dependency. It doesn’t matter if we add installation targets very carefully and ensure that the gtest is not selected for installation. This configuration is very problematic for many C/C++ projects, as there are many situations where we don’t want to install some targets:

  • Static libraries are usually already linked to our targets. There is no need to install them.
  • Header-only libraries are very common in the C++ world. If they are used only internally in translation units and are not exposed to the end user through headers, they should not be installed.
  • While shared libraries are usually necessary during deployment, they are sometimes not. For example, serverless functions execute in a dedicated runtime with a pre-defined environment. Building functions might require linking against certain dependencies, e.g., cloud provider’s SDK for databases. This library might already be available at the deployment site.
  • Internal components, such as testing frameworks, should not be installed or deployed at all.

Contrary to the older solution of using ExternalProject, FetchContent functions do not allow us to disable installation by overriding the installation command with an empty string:

ExternalProject_Add: The default install step builds the install target of the external project, but this can be overridden with a custom command using this option (generator expressions are supported). Passing an empty string as the makes the install step do nothing.

Fortunately, there is an easy solution, but it requires a little more work than just using FetchContent. The call to FetchContent_MakeAvailable populates the dependency, if this has not already been done, and adds its targets to the main configuration. We can replace this call with a slightly more verbose solution:

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()

Now, we can use the EXCLUDE_FROM_ALL flag when manually adding the dependency to the project. Thus, we can still use those targets as previously, but they will not appear in our installation - unless we add them there explicitly.




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • C++ Toolchain with Taint Analysis
  • String formatting with optional values
  • Using LLVM to run Pintools on SPEC benchmarks
  • LLVM Machine Passes
  • Yet another lesson on undefined behavior