Include dependencies in dynamic library

Viewed 486

I am building a library in C++ that requires a few libraries to be included, some of which being GLEW, SDL2, and GLM. I am using CMake to build this library, and have successfully set up (at least to my knowledge) a CMakeLists.txt that adequately does this, but currently without dependencies. I would like to know the proper conventions for adding these external libraries to my own library, keeping in mind that someone on a different machine may be using this library (i.e. not defined file structure/local installs).

This is my current CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(mylib VERSION 1.0.1 LANGUAGES CXX)

set (DEFAULT_BUILD_TYPE "Release")
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
    set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE)
    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

include (GNUInstallDirs)

set (SOURCE_FILES "src/driver.cpp")
add_library(${PROJECT_NAME} ${SOURCE_FILES})

target_include_directories(
    ${PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
    PRIVATE src
)

set_target_properties (
    ${PROJECT_NAME} PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
)

install (
    TARGETS ${PROJECT_NAME} EXPORT mylibConfig
    ARCHIVE  DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY  DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME  DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install (
    DIRECTORY include/ 
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
)

install (
    EXPORT mylibConfig 
    DESTINATION share/mylib/cmake
)

export (
    TARGETS ${PROJECT_NAME}
    FILE mylibConfig.cmake
)

If you similarly notice any key errors/mistakes in my present file, please feel free to let me know, but the more important matter is how I should be properly including these libraries.

3 Answers

To dynamically link GLEW, SDL2, and GLM, you can use the find_package command.

find_package(GLEW REQUIRED)
find_package(SDL2 REQUIRED)
find_package(glm REQUIRED)

Then, after you've called add_library, you will need to link the libraries to your library:

target_link_libraries(mylib PRIVATE GLEW::GLEW SDL2::SDL2 glm::glm)

If you expose any of those dependencies in your API, then you can call target_link_libraries with PUBLIC (instead of PRIVATE) for those dependencies instead.

Consider using a package manager like conan. I took a look for you at conan center and there are recipes for your dependencies. You can follow the getting started guide and it should just work.

In CMake there are many ways to handle dependencies,

  • manually
  • using submodule
  • using fine_package
  • ...

But here I just refer to 2 methods which are easy and portable.

Submodule

If the dependency exists on the github/gitlab or some places that uses git, then you can easily use submodule method.

with submodule your dependencies can be updated always(through their github/gitlab pages), but you can't change them yourself because your changes will be locally(like every time you clone a repository, your changed will be locally unless you do a pull request or be a contributor).

Usage

For using submodule, you need to have this section on your .CMakeLists.txt file:

#--------------------------------------------------------------
# submodule section
# here we use the following code to support
# old versions of git(that don't download submodule
# contents automatically).
#--------------------------------------------------------------
# [[[
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
  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 failed with ${GIT_SUBMODULE_RESULT}, Please checkout submodules")
    endif()
  endif()
endif()
# ]]]
#

This easily run git submodule update --init --recursive automatically for you. and then you can use your submodule like this:

# your submodule directory that has a main .CMakeLists.txt file inside it.
add_subdirectory("YOUR_SUBMODULE_DIRECTORY")

target_include_directories(
  executable_or_library_name PRIVATE
  YOUR_SUBMODULE_DIRECTORY/include
)

Here's an example for submodule.

find_package

If the dependency has a find_package support, then you use find_package method, specially if it has this support and it's not on the github/gitlab or a place that uses git, you should use this method then for sure.

Usage

It's also easy to use, with just most of the times one command:

find_package(dependency)

target_include_directories(YOUR_TARGET 
"${dependency_INCLUDE_DIR}"
# ... your other include directories
)

target_link_libraries(YOUR_TARGET 
"${dependency_LIBRARIES}"
# ... your other libraries to add
)

There are many possible arguments like REQUIRED and COMPONENTS that are explained in the official cmake website

Not part of this question but useful (Side Note)

I usually have this at the end of my main .CMakeLists.txt to support emacs/vim/... and every editors which uses LSP for finding symbols:

#--------------------------------------------------------------
# Generating compile_commands.json file for lsp servers
#--------------------------------------------------------------
# [[[
option(CMAKE_EXPORT_COMPILE_COMMANDS "Generate lsp command file" ON)

if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json")
  execute_process(
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json
    ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json
  )
endif()
# ]]]
#--------------------------------------------------------------
Related