0

I have a project where I want to use the nghttp2 library. I am trying to link this project in CMakeLists.txt so that I can compile the following file with cmake.

My project structure

.
├── CMakeLists.txt
├── lib
│   ├── Catch2
│   └── nghttp2
├── README.md
└── src
    ├── main
    |  └── Main.cpp
    └── test

The main program that imports nghttp2

#include <iostream>
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
#include <boost/circular_buffer.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/thread.hpp>
#include <boost/timer/timer.hpp>
#include <boost/call_traits.hpp>
#include <boost/bind.hpp>
#include <boost/log/trivial.hpp>
#include <string>
#include <limits>

using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;

int main(int argc, char *argv[]) {
    boost::system::error_code ec;
    http2 server;

    server.handle("/", [](const request &req, const response &res) {
        res.write_head(200);
        res.end("hello, world\n");
    });

    if (server.listen_and_serve(ec, "localhost", "3000")) {
        std::cerr << "error: " << ec.message() << std::endl;
    }
}

My CMakeLists.txt file is the following

cmake_minimum_required(VERSION 3.16.3)
project(my-project)

set(CMAKE_CXX_STANDARD 14)

set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)
set(LIBRARY_NAME ${PROJECT_NAME})
set(TEST_NAME tests)

add_executable(
        ${PROJECT_NAME}

        src/main/Main.cpp
)

find_package(Threads REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)
find_package(Boost REQUIRED COMPONENTS thread)
find_package(Boost REQUIRED COMPONENTS log)
find_package(OpenSSL REQUIRED)

target_link_libraries(
        ${PROJECT_NAME}

        PRIVATE Threads::Threads
        PRIVATE Boost::system
        PRIVATE Boost::thread
        PRIVATE Boost::log
        PRIVATE OpenSSL::Crypto
)

set(NGHTTP2_SRC_DIR lib/nghttp2)
add_subdirectory(${NGHTTP2_SRC_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC nghttp2)

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


# Catch2
set(CATCH2_SRC_DIR lib/Catch2)
add_subdirectory(${CATCH2_SRC_DIR})
add_executable(${TEST_NAME} src/test/Test.cpp)
target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain)
include(${CATCH2_SRC_DIR}/extras/Catch.cmake)
include(${CATCH2_SRC_DIR}/extras/ParseAndAddCatchTests.cmake)
list(APPEND CMAKE_MODULE_PATH ${CATCH2_SRC_DIR}/extras)
include(CTest)
catch_discover_tests(${TEST_NAME})

When I run make after cmake . I get the following error

/usr/bin/ld: CMakeFiles/my-project.dir/src/main/Main.cpp.o: in function `std::_Function_handler<void (nghttp2::asio_http2::server::request const&, nghttp2::asio_http2::server::response const&), main::{lambda(nghttp2::asio_http2::server::request const&, nghttp2::asio_http2::server::response const&)#1}>::_M_invoke(std::_Any_data const&, nghttp2::asio_http2::server::request const&, nghttp2::asio_http2::server::response const&)':
/home/username/CLionProjects/myproject/src/main/Main.cpp:23: undefined reference to `nghttp2::asio_http2::server::response::write_head(unsigned int, std::multimap<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, nghttp2::asio_http2::header_value, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, nghttp2::asio_http2::header_value> > >) const'
/usr/bin/ld: CMakeFiles/my-project.dir/src/main/Main.cpp.o: in function `operator()':
/home/username/CLionProjects/myproject/src/main/Main.cpp:24: undefined reference to `nghttp2::asio_http2::server::response::end(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) const'
/usr/bin/ld: CMakeFiles/my-project.dir/src/main/Main.cpp.o: in function `main.cold':
/home/username/CLionProjects/myproject/src/main/Main.cpp:20: undefined reference to `nghttp2::asio_http2::server::http2::~http2()'
/usr/bin/ld: CMakeFiles/my-project.dir/src/main/Main.cpp.o: in function `main':
/home/username/CLionProjects/myproject/src/main/Main.cpp:20: undefined reference to `nghttp2::asio_http2::server::http2::http2()'
/usr/bin/ld: /home/username/CLionProjects/myproject/src/main/Main.cpp:22: undefined reference to `nghttp2::asio_http2::server::http2::handle(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::function<void (nghttp2::asio_http2::server::request const&, nghttp2::asio_http2::server::response const&)>)'
/usr/bin/ld: /home/username/CLionProjects/myproject/src/main/Main.cpp:27: undefined reference to `nghttp2::asio_http2::server::http2::listen_and_serve(boost::system::error_code&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool)'
/usr/bin/ld: /home/username/CLionProjects/myproject/src/main/Main.cpp:20: undefined reference to `nghttp2::asio_http2::server::http2::~http2()'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/my-project.dir/build.make:95: build/my-project] Error 1
make[1]: *** [CMakeFiles/Makefile2:942: CMakeFiles/my-project.dir/all] Error 2
make: *** [Makefile:141: all] Error 2

I can manually compile the Main.cpp with

g++  Main.cpp -o Main.out -DBOOST_LOG_DYN_LINK -lboost_log -lnghttp2_asio -lboost_system -lcrypto -lpthread -lssl -lboost_thread

Edit:

I got it to compile by first having nghttp2 installing on my machine. Then adding this line in CMakeLists.txt

target_link_libraries(${PROJECT_NAME} PUBLIC /usr/local/lib/libnghttp2_asio.so)

Is there a more elegant way to do this?

  • In the command line you link with `nghttp2_asio` library, but I see no such linkage in your `CMakeLists.txt`. – Tsyvarev Aug 30 '21 at 21:25
  • @Tsyvarev That is true. I'm asking how to link nghttp2_asio –  Aug 31 '21 at 08:09

1 Answers1

0

To answer the question you added after editing your initial message, you can avoid specifying the full path to libnghttp2_asio.so manually by using CMake's find_library, like this:

find_library(nghttp2_asio_LIBRARY NAMES nghttp2_asio)
target_link_libraries(${PROJECT_NAME} PUBLIC nghttp2_asio)

However, you are mixing two different ways of adding an external library to your project. Since you are already including nghttp2's source code in your project, you should not link your program against another version of nghttp2 that you installed system-wide for at least two reasons:

  • The nghttp2 headers your are including in your project might not match the version of the nghttp2 library you installed system-wide, which might cause linking errors
  • If someone clones your code, they will have to install nghttp2 on their system as well, and they might run into the very same linking errors explained in the previous point

Instead, I suggest you build libnghttp2_asio from source, as you initially planned.

To solve your initial issue, you should link against the right library (nghttp2_asio instead of nghttp2) and set the flag --enable-asio-lib when building nghttp2. Here is what your CMake should look like:

set(NGHTTP2_SRC_DIR lib/nghttp2)
set(ENABLE_ASIO_LIB ON)
add_subdirectory(${NGHTTP2_SRC_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC nghttp2_asio)

In addition, you will have to add Boost::asio to the list of libraries in your first call to target_link_libraries, as explained in nghttp2's documentation:

libnghttp2_asio is not built by default. Use --enable-asio-lib configure flag to build libnghttp2_asio. The required Boost libraries are: Boost::Asio, Boost::System and Boost::Thread.