13

I have a simple project which requires three header-only libraries in order to compile: websocketpp, spdlog and nlohmann/json.

The project structure looks like this:

└── src
    ├── app
    │   ├── CMakeLists.txt
    │   ├── src
    │   └── test
    ├── CMakeLists.txt
    ├── core
    │   ├── CMakeLists.txt
    │   ├── include
    │   ├── src
    │   └── test
    └── vendor
        ├── install.cmake
        ├── nlohmann_json
        ├── spdlog
        └── websocketpp

The root CMakeLists.txt is as follows:

cmake_minimum_required(VERSION 3.6.1 FATAL_ERROR)

..

# External 3rd party libs that we include

include(vendor/install.cmake)

add_subdirectory(core)
add_subdirectory(app)

The idea is basically that each subdirectory is a library (e.g. core), and app "aggregates" all of them. Each library (e.g. core) is built like this (core/CMakeLists.txt):

project(foo-core VERSION 0.1 LANGUAGES CXX)
add_library(foo-core
  src/foobar/foobar.cc
  src/foobaz/baz.cc)

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

target_link_libraries(foo-core websocketpp spdlog) # <- see here, using spdlog & websocketpp

# 'make install' to the correct location

install(TARGETS foo-core EXPORT FooCoreConfig
  ARCHIVE  DESTINATION lib
  LIBRARY  DESTINATION lib
  RUNTIME  DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)

install(EXPORT FooCoreConfig DESTINATION share/FooCore/cmake)

export(TARGETS foo-core FILE FooCoreConfig.cmake)

Notice how I link the dependencies (which are header-only libraries!). This is how I fetch them (vendor/install.cmake):

# spdlog

if((NOT SPDLOG_INCLUDE_DIR) OR (NOT EXISTS ${SPDLOG_INCLUDE_DIR}))
  message("Unable to find spdlog, cloning...")

  execute_process(COMMAND git submodule update --init -- vendor/spdlog
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

  set(SPDLOG_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/spdlog/include/
    CACHE PATH "spdlog include directory")

  install(DIRECTORY ${SPDLOG_INCLUDE_DIR}/spdlog DESTINATION include)

  # Setup a target

  add_library(spdlog INTERFACE)
  target_include_directories(spdlog INTERFACE
    $<BUILD_INTERFACE:${SPDLOG_INCLUDE_DIR}>
    $<INSTALL_INTERFACE:include>)

  install(TARGETS spdlog EXPORT spdlog DESTINATION include)
endif()

# websocketpp

if((NOT WEBSOCKETPP_INCLUDE_DIR) OR (NOT EXISTS ${WEBSOCKETPP_INCLUDE_DIR}))
  message("Unable to find websocketpp, cloning...")

  execute_process(COMMAND git submodule update --init -- vendor/websocketpp
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

  set(WEBSOCKETPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/websocketpp/
    CACHE PATH "websocketpp include directory")

  install(DIRECTORY ${WEBSOCKETPP_INCLUDE_DIR}/websocketpp DESTINATION include)

  # Setup a target

  add_library(websocketpp INTERFACE)
  target_include_directories(websocketpp INTERFACE
    $<BUILD_INTERFACE:${WEBSOCKETPP_INCLUDE_DIR}>
    $<INSTALL_INTERFACE:include>)

  install(TARGETS websocketpp EXPORT websocketpp DESTINATION include)
endif()

# nlohmann/json

if((NOT NLOHMANN_JSON_INCLUDE_DIR) OR (NOT EXISTS ${NLOHMANN_JSON_INCLUDE_DIR}))    
  message("Unable to find nlohmann/json, cloning...")

  execute_process(COMMAND git submodule update --init -- vendor/nlohmann_json
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

  set(NLOHMANN_JSON_INCLUDE_DIR
    ${CMAKE_CURRENT_SOURCE_DIR}/vendor/nlohmann_json/src/
    CACHE PATH "nlohmann/json include directory")

  install(FILES ${NLOHMANN_JSON_INCLUDE_DIR}/json.hpp DESTINATION include)

  # Setup a target

  add_library(nlohmann_json INTERFACE )
  target_include_directories(nlohmann_json INTERFACE
    $<BUILD_INTERFACE:${NLOHMANN_JSON_INCLUDE_DIR}>
    $<INSTALL_INTERFACE:include>)

  install(TARGETS nlohmann_json EXPORT nlohmann_json DESTINATION include)
endif()

So far so good: you can see the dependencies are fetched as git submodules, which thankfully makes it easier to manage them. However, when I compile my project with mkdir build && cd build && cmake ../src, I have the following errors:

CMake Error: install(EXPORT FooCoreConfig ...) includes target foo-core which requires target websocketpp that is not in the export set.

CMake Error: install(EXPORT FooCoreConfig ...) includes target foo-core which requires target spdlog that is not in the export set.

Including the headers e.g. #include <spdlog/spdlog.h> or #include <nlohmann/json.hpp> produces error saying that the header is not found.

Truth be told, I am not so comfortable with CMake and I have spent the past two days debugging this. It might be something really simple, but I can't figure how to achieve it. In reality, one would just pass -I as a compiler flag to use the libraries like I want, but the CMake abstraction seems to confuse me. I would be very glad if someone could explain why this doesn't work and, hopefully, what's the correct way of including these libraries to my project. Thanks in advance!

rgmt
  • 13,342
  • 11
  • 43
  • 62
arnaudoff
  • 656
  • 7
  • 19

1 Answers1

2

Just as you're said: you didn't install your targets in the export set. In other words, you're missing an install(EXPORT ... line for your header-only targets. For example, considering your header-only library websocketpp, you should have:

add_library(websocketpp INTERFACE)
target_include_directories(websocketpp INTERFACE
  $<BUILD_INTERFACE:${WEBSOCKETPP_INCLUDE_DIR}>
  $<INSTALL_INTERFACE:include>)

install(TARGETS websocketpp EXPORT websocketpp-config DESTINATION include)

# here is the missing line:

install(EXPORT websocketpp-config DESTINATION share/websocketpp/cmake)

Same goes for the other libraries.

rgmt
  • 13,342
  • 11
  • 43
  • 62
  • Thanks, I added it, however, I get the same message for the `websocketpp` library: "CMake Error in core/CMakeLists.txt: export called with target foo-core which requires target websocketpp that is not in the export set" and "If the required target is not easy to reference in this call, consider using the APPEND option with multiple separate calls.". Weird enough, the file seems to be under `build/CMakeFiles/Export/share/websocketpp/cmake/websocketpp.cmake`, why would it not find it? – arnaudoff Dec 27 '16 at 15:35
  • @arnaudoff that may come from the name of the file, which should follow a given pattern. I edited my answer, try with the modified name `websocketpp-config` – rgmt Dec 27 '16 at 15:58
  • @arnaudoff I think I get it: remove the `export` line from `core/CMakeLists.txt`. This shouldn't be used for target installation. You will generate the export when doing `make install` – rgmt Dec 27 '16 at 16:56
  • Alright, this fixed the issue with the export set, but I'm still getting the header includes errors, e.g: "fatal error: nlohmann/json.hpp: No such file or directory #include ". I guess it has to do something with the paths in the generator expressions, but they look fine to me at least. Any ideas? – arnaudoff Dec 27 '16 at 18:00
  • @arnaudoff looking in your code, the `set` and the `install` line suggest the path to `json.hpp` is `${CMAKE_CURRENT_SOURCE_DIR}/vendor/nlohmann_json/src/json.hpp` at build time, which is not consistent with a `#include `. You should have `#include "json.hpp"` (note the use of `""` instead of `<>` which are for system headers). Check your paths, and make them consistent – rgmt Dec 28 '16 at 13:21