Cpp Notes

5.including_other_project

Including other projects

Git submodule in CMake

Adding a Git Submodule

To add a Git submodule (for example, to the extern/ directory) from a repository hosted on the same service (e.g., GitHub, GitLab, BitBucket):

gitbook $ git submodule add ../../owner/repo.git extern/repo
  • Relative path: Keeps the access method (SSH or HTTPS) consistent with the parent repository.
  • Inside the submodule: It behaves like a normal Git repo.
  • In the parent repository: You can update the submodule by changing the current commit pointer.

Typical Submodule Drawbacks

Users need to know Git submodule commands to initialize and update the submodule, or they must clone the repository with the --recursive option. However, CMake can automate this process.

CMake Submodule Handling

find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
    # Update submodules as needed
    option(GIT_SUBMODULE "Check submodules during build" ON)
    if(GIT_SUBMODULE)
        message(STATUS "Submodule update")
        execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
                        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                        RESULT_VARIABLE GIT_SUBMOD_RESULT)
        if(NOT GIT_SUBMOD_RESULT EQUAL "0")
            message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
        endif()
    endif()
endif()

if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt")
    message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
endif()
  1. Checking for Git:

    • The script checks if Git is installed using CMake’s FindGit.cmake.
  2. Submodule Update Logic:

    • Adds an option GIT_SUBMODULE (default ON) to let developers disable submodule checks if necessary.
    • Runs the Git command to initialize and update the submodules:
      git submodule update --init --recursive
    • Displays a clear error message if the update fails.
  3. Verification:

    • Verifies that the submodule files (like CMakeLists.txt) exist, ensuring the submodule is correctly initialized.

Including Submodules in the Build

  • Use add_subdirectory(extern/repo) to include submodules that provide CMake support.
  • For header-only projects: You can build an interface library target yourself if needed.
  • Using find_package: If supported, you can use find_package to locate the submodule, especially if you have a Find*.cmake file.

Enhancing CMake Support

  • You can append directories to CMAKE_MODULE_PATH to add helper CMake files (e.g., adding pybind11's improved FindPython*.cmake files).

Git Version Numbe

To capture the current Git commit hash as the version number, use the following CMake code:

execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
                WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
                OUTPUT_VARIABLE PACKAGE_GIT_VERSION
                ERROR_QUIET
                OUTPUT_STRIP_TRAILING_WHITESPACE)

This retrieves the short version of the Git commit hash and stores it in PACKAGE_GIT_VERSION, which can be used elsewhere in the build system.

FetchContent in CMake (3.11+)

FetchContent is a CMake module introduced in version 3.11 that allows downloading and integrating external dependencies as part of the configure step, instead of the build step. It consolidates functionality that was previously implemented in various third-party modules.

Key Features

  • FetchContent_Declare(MyName): Declares a dependency by specifying a URL, Git repository, or other source.
  • FetchContent_GetProperties(MyName): Retrieves properties related to the dependency, such as its source and binary directories.
  • FetchContent_Populate(MyName): Downloads and prepares the content if not already available.
  • add_subdirectory("${MyName_SOURCE_DIR}" "${MyName_BINARY_DIR}"): Adds the downloaded content to your project.

Example: Downloading Catch2

To download and use Catch2 in your project with CMake 3.14+:

FetchContent_Declare(
  catch
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        v2.13.6
)

# CMake 3.14+ provides a simplified syntax
FetchContent_MakeAvailable(catch)

CMake 3.11-3.13 Method:

If you're using CMake 3.11-3.13, the following is the classic way to fetch and integrate a dependency:

FetchContent_GetProperties(catch)
if(NOT catch_POPULATED)
  FetchContent_Populate(catch)
  add_subdirectory(${catch_SOURCE_DIR} ${catch_BINARY_DIR})
endif()

Macro for CMake 3.11-3.13:

To enable the simplified FetchContent_MakeAvailable() syntax in older versions (pre-3.14), you can create a macro:

if(${CMAKE_VERSION} VERSION_LESS 3.14)
    macro(FetchContent_MakeAvailable NAME)
        FetchContent_GetProperties(${NAME})
        if(NOT ${NAME}_POPULATED)
            FetchContent_Populate(${NAME})
            add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
        endif()
    endmacro()
endif()

This macro replicates the CMake 3.14+ functionality, allowing you to use the modern syntax in older versions.

More Information