3

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.

Timesis
  • 369
  • 3
  • 14
  • how did you add your dependencies to your project? manually, submodule, findpackage? – SdSaati Aug 06 '21 at 23:28
  • @Saeid That is part of my question, I am wondering the best practices for this, whether it be through findpackage etc. Portability is important to me, so any answer would need to allow for that. – Timesis Aug 06 '21 at 23:34
  • `submodule` method and `findpackage` both are portable, if you don't need to change your dependencies(ex: SDL) yourself, you can go with submodule, it's so easy to use and also it's portable, some times your dependencies doesn't have findpackage module, that's also a good time for using `submodule`, but if the dependency has findpackage, you also can use `findpackage` and it's also portable. – SdSaati Aug 06 '21 at 23:37
  • @Saeid If by change you mean modify the dependency, then no I do not. Both of these options do sound viable, I am unfamiliar with submodule usage however. Would these methods also allow for scalability, as the 3 packages I mentioned are just the tip of the iceberg. – Timesis Aug 06 '21 at 23:47
  • `submodule` is a about git, if your packages are on github/gitlab/..., then you can use submodule for all of them, and you can even update your submodules to the updated versions that are on github,... – SdSaati Aug 06 '21 at 23:49
  • I don't want to say find_package is bad and submodule is good, both are methods but, sometimes your dependency doesn't have find_package, and it's on the github, in this case submodule is a good way to go, and sometimes your dependency isn't on github, but have find_package, here you can use find_package, also sometimes both are available, then you can use both as you wish. – SdSaati Aug 06 '21 at 23:52

3 Answers3

4

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.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
Peter Lilley
  • 119
  • 1
  • 6
  • I have edited your answer to use imported targets. When imported targets are available, _they should always be used_. – Alex Reinking Aug 09 '21 at 03:36
  • Upon running into an error such as ```Could NOT find GLEW (missing: GLEW_INCLUDE_DIRS GLEW_LIBRARIES)```, would the common ```list(APPEND CMAKE_PREFIX_PATH )``` solution be adequate, or is there a better way to allow CMake to find this? – Timesis Aug 13 '21 at 20:50
3

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.

Koronis Neilos
  • 700
  • 2
  • 6
  • 20
2

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()
# ]]]
#--------------------------------------------------------------
SdSaati
  • 798
  • 9
  • 18
  • For using ```find_package()```, what is the proper way of including the dependency folder in the project, would it need to be included in my library's structure, or would it simply require the user of my library to have them locally installed and imported on their side? Similarly, when using ```find_package()```, what would be the proper way to link it, with ```target_link_libraries()``` or something else? – Timesis Aug 07 '21 at 18:41
  • @Timesis always in modern cmake, use the target_*_() functions, and yes, if you use find_package(or even submodule), you need to use `target_link_libraries()` to add that library to your target(library/executable), and yes it's the best way of modern cmake to use that function. sometimes, you can check the errors and warnings, depends on the library you are adding, you maybe need to use `target_include_directories()` as well! – SdSaati Aug 08 '21 at 02:38
  • How could I make the ```target_include_directories``` compatible with my current orientation presented in my original question? – Timesis Aug 14 '21 at 23:20
  • @Timesis Your `target_include_directories` is Ok, but you just need to add your library's include folders to it `"${dependency_INCLUDE_DIR}"`, dependency can be any include folder of any library that you are using it. – SdSaati Aug 15 '21 at 11:45