I still consider myself a fairly novice C++ programmer and with CMake I have even less experience.
In an Eclipse CMake project I have written a shared library for accessing an XML database (BaseX). In the IDE the code compiles without errors to a shared object. In another project I wrote a test program that links to the library. This program compiles with the public API and works as expected.
According to this article ‘Modern CMake tutorial for C++ library developers’, it should be able to package a library. This article provides a template that should make packaging easy (GitHub - pananton/cpp-lib-template: Template for C++ library built with CMake).
- In this template I first replaced all occurrences of
Mylib
and toMYLIB
toBasexCpp
respectivelyBASEXCPP
. - The commands
cmake -S . -B Build/Debug --fresh --preset=dev-linux -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -G Ninja
andninja
(after cd to Build/Debug) result in a shared library with only 1 function (Add) and an executable (libTest
). This program uses theAdd
function that is provided by the library.
I then added the dependency with Curl in CMakeLists.txt and copied the source code of my library to the directory tree. The new CMakeLists.txt, the public API BasexCpp.h
and the testfile main.cpp
are added below.
Even after adding my sources, the test program works. However, when I uncomment the code in main.cpp, ninja returns this error message:
[11/11] Linking CXX executable examples/add/libTest
FAILED: examples/add/libTest
: && /usr/lib64/ccache/c++ -std=c++20 -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wcast-align -Wcast-qual -Wnull-dereference -Woverloaded-virtual -Wformat=2 -Werror -g examples/add/CMakeFiles/libTest.dir/main.cpp.o -o examples/add/libTest -Wl,-rpath,/home/bengbers/Thuis/CPP_LIB4/Build/Debug libBasexCpp.so.1.0.0 -Llib64 -lcurlpp -L/usr/lib64 -lcurl && :
/usr/bin/ld: examples/add/CMakeFiles/libTest.dir/main.cpp.o: in function `main':
/home/bengbers/Thuis/CPP_LIB4/examples/add/main.cpp:19:(.text+0xf6): undefined reference to `BasexCpp::BasexClient::BasexClient(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int const&, 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&)'
/usr/bin/ld: /home/bengbers/Thuis/CPP_LIB4/examples/add/main.cpp:21:(.text+0x154): undefined reference to `BasexCpp::BasexClient::Create(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&)'
/usr/bin/ld: /home/bengbers/Thuis/CPP_LIB4/examples/add/main.cpp:24:(.text+0x185): undefined reference to `BasexCpp::BasexClient::~BasexClient()'
/usr/bin/ld: /home/bengbers/Thuis/CPP_LIB4/examples/add/main.cpp:24:(.text+0x1f8): undefined reference to `BasexCpp::BasexClient::~BasexClient()'
collect2: fout: ld gaf exit-status 1 terug
ninja: build stopped: subcommand failed.
I don’t know how to interpret the results from the command readelf -s Build/Debug/libBasexCpp.so > ReadElf.out
but after some googling I learned that readelf
can be used to inspect the contents from the library.
This is part of ReadElf.out
9: 0000000000000000 0 FILE LOCAL DEFAULT ABS Add.cpp
10: 0000000000000000 0 FILE LOCAL DEFAULT ABS Base.cpp
11: 00000000000451c7 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail30[...]
12: 00000000000451c8 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail30[...]
13: 00000000000451c9 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail30[...]
14: 00000000000451ca 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail14[...]
15: 00000000000451cb 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail19[...]
16: 00000000000451cc 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail14[...]
17: 00000000000451cd 1 OBJECT LOCAL DEFAULT 14 _ZNSt8__detail19[...]
I should expect that the lines 11-17 would export the Base-constructor.
Cane someone help me and tell me what I am doing wrong?
Ben
CmakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(BasexCpp
VERSION 1.0.0
DESCRIPTION "Template for C++ library built with CMake"
LANGUAGES CXX)
#----------------------------------------------------------------------------------------------------------------------
# general settings and options
#----------------------------------------------------------------------------------------------------------------------
include(cmake/utils.cmake)
include(GNUInstallDirs)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" is_top_level)
# BASEXCPP_SHARED_LIBS option (undefined by default) can be used to force shared/static build
set(BASEXCPP_SHARED_LIBS ON)
option(BASEXCPP_BUILD_TESTS "Build BasexCpp tests" OFF)
# option(BASEXCPP_BUILD_EXAMPLES "Build BasexCpp examples" ON)
option(BASEXCPP_BUILD_DOCS "Build BasexCpp documentation" OFF)
option(BASEXCPP_INSTALL "Generate target for installing BasexCpp" ${is_top_level})
set_if_undefined(BASEXCPP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/BasexCpp" CACHE STRING
"Install path for BasexCpp package-related CMake files")
if(DEFINED BASEXCPP_SHARED_LIBS)
set(BUILD_SHARED_LIBS ${BASEXCPP_SHARED_LIBS})
endif()
if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
set_if_undefined(CMAKE_CXX_VISIBILITY_PRESET hidden)
set_if_undefined(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
add_library(BasexCpp) # initialized below
add_library(BasexCpp::BasexCpp ALIAS BasexCpp)
#----------------------------------------------------------------------------------------------------------------------
# BasexCpp dependencies
#----------------------------------------------------------------------------------------------------------------------
# Search for your dependencies here
include(FindPkgConfig)
pkg_check_modules(CURLPP REQUIRED curlpp)
include(FindCURL)
find_package(CURL REQUIRED)
if(NOT CURL_FOUND)
message(FATAL_ERROR "Could not find CURL")
else()
target_link_libraries(${PROJECT_NAME} ${CURLPP_LDFLAGS})
endif()
#----------------------------------------------------------------------------------------------------------------------
# BasexCpp sources
#----------------------------------------------------------------------------------------------------------------------
include(GenerateExportHeader)
set(export_file_name "export_shared.h")
if(NOT BUILD_SHARED_LIBS)
set(export_file_name "export_static.h")
endif()
generate_export_header(BasexCpp EXPORT_FILE_NAME include/BasexCpp/${export_file_name})
target_include_directories(BasexCpp PRIVATE src)
set(public_headers
include/BasexCpp/export.h
# include/BasexCpp/Add.h
include/BasexCpp/BasexCpp.h
)
set(sources
${public_headers}
src/Add.cpp
src/Base.cpp
src/BasexClient.cpp
src/BasexSocket.cpp
src/ClientUtils.cpp
src/QueryObject.cpp
src/ResponseObj.cpp
src/BasexClient.h
src/BasexSocket.h
src/ClientUtils.h
src/QueryObject.h
src/ResponseObj.h
)
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${sources})
#----------------------------------------------------------------------------------------------------------------------
# BasexCpp target
#----------------------------------------------------------------------------------------------------------------------
include(CMakePackageConfigHelpers)
target_sources(BasexCpp PRIVATE ${sources})
target_compile_definitions(BasexCpp PUBLIC "$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:BASEXCPP_STATIC_DEFINE>")
target_include_directories(BasexCpp
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
set_target_properties(BasexCpp PROPERTIES
SOVERSION ${PROJECT_VERSION_MAJOR}
VERSION ${PROJECT_VERSION})
if(BASEXCPP_INSTALL AND NOT CMAKE_SKIP_INSTALL_RULES)
configure_package_config_file(cmake/BasexCpp-config.cmake.in BasexCpp-config.cmake
INSTALL_DESTINATION "${BASEXCPP_INSTALL_CMAKEDIR}")
write_basic_package_version_file(BasexCpp-config-version.cmake
COMPATIBILITY SameMajorVersion)
install(TARGETS BasexCpp EXPORT BasexCpp_export
RUNTIME COMPONENT BasexCpp
LIBRARY COMPONENT BasexCpp NAMELINK_COMPONENT BasexCpp-dev
ARCHIVE COMPONENT BasexCpp-dev
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(DIRECTORY include/
TYPE INCLUDE
COMPONENT BasexCpp-dev)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/BasexCpp/${export_file_name}"
COMPONENT BasexCpp-dev
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/BasexCpp")
set(targets_file "BasexCpp-shared-targets.cmake")
if(NOT BUILD_SHARED_LIBS)
set(targets_file "BasexCpp-static-targets.cmake")
endif()
install(EXPORT BasexCpp_export
COMPONENT BasexCpp-dev
FILE "${targets_file}"
DESTINATION "${BASEXCPP_INSTALL_CMAKEDIR}"
NAMESPACE BasexCpp::)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/BasexCpp-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/BasexCpp-config-version.cmake"
COMPONENT BasexCpp-dev
DESTINATION "${BASEXCPP_INSTALL_CMAKEDIR}")
if(MSVC)
set(pdb_file "")
set(pdb_file_destination "")
if(BUILD_SHARED_LIBS)
set(pdb_file "$<TARGET_PDB_FILE:BasexCpp>")
set(pdb_file_destination "${CMAKE_INSTALL_BINDIR}")
else()
# TARGET_PDB_FILE does not work for pdb file generated for static library build, determining it manually
set(pdb_file "$<TARGET_FILE_DIR:BasexCpp>/$<TARGET_FILE_PREFIX:BasexCpp>$<TARGET_FILE_BASE_NAME:BasexCpp>.pdb")
set(pdb_file_destination "${CMAKE_INSTALL_LIBDIR}")
endif()
install(FILES "${pdb_file}"
COMPONENT BasexCpp-dev
CONFIGURATIONS Debug RelWithDebInfo
DESTINATION "${pdb_file_destination}"
OPTIONAL)
endif()
endif()
#----------------------------------------------------------------------------------------------------------------------
# other targets
#----------------------------------------------------------------------------------------------------------------------
if(BASEXCPP_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
if(BASEXCPP_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if(BASEXCPP_BUILD_DOCS)
find_package(Doxygen REQUIRED)
doxygen_add_docs(docs include)
endif()
BasexCpp.h
Edit
I have added BASEXCPP_EXPORT to all the class methods that should be exported.
Also I added another testfunction (minus').
minusand
add` will be removed in the final library.
#pragma once
#ifndef BASEXCPP_H_
#define BASEXCPP_H_
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <wordexp.h>
#include <iostream>
#include <list>
#include <sys/types.h>
#include <stdlib.h>
typedef std::vector < std::byte > ByteVect;
typedef std::vector < ByteVect> VectOfByteVect;
#include <memory>
class BasexSocket;
typedef std::shared_ptr < BasexSocket > BasexSock_Sptr;
#include <utility>
#include <cassert>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include <array>
#include <regex>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>
#include <cstdio>
#ifdef __WIN32__
#include <winsock2.h>
#include <Windows.h>
#else
#include <sys/socket.h>
#include <unistd.h>
#endif
#include <BasexCpp/export.h>
using namespace std;
namespace BasexCpp {
BASEXCPP_EXPORT int add(int a, int b);
BASEXCPP_EXPORT int minus(int a, int b);
class QueryObject;
typedef std::unique_ptr < QueryObject > QueryObj_Uptr;
// 1 ### #include "ClientUtils.h"
BASEXCPP_EXPORT ByteVect getBytes (string const &s);
BASEXCPP_EXPORT string getChar (ByteVect const &b);
BASEXCPP_EXPORT string pathExpander (const string & s, int flags = 0);
BASEXCPP_EXPORT std::ostream & debug_dump (const ByteVect & str, std::ostream & stm = std::cout);
BASEXCPP_EXPORT std::ostream & debug_dump (const string & str, std::ostream & stm = std::cout);
// 2 ### #include "Base.h"
class BASEXCPP_EXPORT Base {
public:
BASEXCPP_EXPORT explicit Base (const string &, const int &, const string &, const string &);
BASEXCPP_EXPORT explicit Base (const string &, const string &, const string &, const string &);
BASEXCPP_EXPORT explicit Base (BasexSock_Sptr socket);
protected:
BasexSock_Sptr Socket;
};
// 3 ### #include "ResponseObj.h"
class BASEXCPP_EXPORT ResponseObj {
public:
ResponseObj ();
BASEXCPP_EXPORT explicit ResponseObj (const string &, ByteVect &);
};
// 4 ### #include "QueryObject.h"
class BASEXCPP_EXPORT QueryObject: virtual public Base {
public:
BASEXCPP_EXPORT void Close ();
BASEXCPP_EXPORT void Bind (const string & name, const string & value, const string & type = "");
BASEXCPP_EXPORT void Bind (const string & name, const std::list < string > &valueList, const string & type = "");
BASEXCPP_EXPORT void Bind (const string & name, const std::list < string > &valueList, const std::list < string > &typeList);
BASEXCPP_EXPORT ByteVect Next ();
BASEXCPP_EXPORT bool More ();
BASEXCPP_EXPORT void Exec ();
BASEXCPP_EXPORT void Info ();
BASEXCPP_EXPORT void Options ();
BASEXCPP_EXPORT void Context (const string & value, const string & type = "");
BASEXCPP_EXPORT void Updating ();
BASEXCPP_EXPORT void Full ();
BASEXCPP_EXPORT string getQueryString ();
BASEXCPP_EXPORT string getStatus ();
BASEXCPP_EXPORT ByteVect getResultBytes ();
BASEXCPP_EXPORT ByteVect getResult ();
BASEXCPP_EXPORT string getResultString ();
BASEXCPP_EXPORT string asString (const ByteVect & vect);
ResponseObj Response;
};
// 5 ### #include "BasexClient.h"
class BASEXCPP_EXPORT BasexClient: virtual public Base {
public:
BASEXCPP_EXPORT explicit BasexClient (const string &, const int &, const string &, const string &);
BASEXCPP_EXPORT explicit BasexClient (const string &, const string &, const string &, const string &);
BASEXCPP_EXPORT virtual ~ BasexClient ();
BASEXCPP_EXPORT void Execute (const string & command);
BASEXCPP_EXPORT void Create (const string & dbName, const string & content = "");
BASEXCPP_EXPORT void Add (const string & path, const string & input);
BASEXCPP_EXPORT void Put (const string & path, const string & input);
BASEXCPP_EXPORT void PutBinary (const string & path, const ByteVect & input);
BASEXCPP_EXPORT QueryObj_Uptr Query (const string & query);
BASEXCPP_EXPORT string getCaller ();
BASEXCPP_EXPORT string getInfo ();
BASEXCPP_EXPORT string getStatus ();
BASEXCPP_EXPORT ByteVect getResultBytes ();
BASEXCPP_EXPORT ByteVect getResult ();
};
}
#endif // BASEXCPP_H_
Main.cpp
#include <BasexCpp/BasexCpp.h>
#include <iostream>
using namespace std;
using namespace BasexCpp;
int main(int, char*[])
{
// auto sum = BasexCpp::add(3, 1);
auto sum = add(3, 1);
std::cout << sum << std::endl;
/*
string DBHOST("localhost");
int DBPORT(1984);
string DBUSERNAME("Test");
string DBPASSWORD("testBaseX");
BasexClient Session (DBHOST, DBPORT, DBUSERNAME, DBPASSWORD);
Session.Create("TestCpp");
*/
return 0;
}