How to link debug executable with release library using CMake FetchContent? - c++

I would like to link my Debug executable with external library built in Release version using FetchContent.
I'm able to link my Debug executable with Debug built library and similar with Release and Release using:
project(CMakeDemo)
set(FETCHCONTENT_QUIET OFF)
FetchContent_Declare(
ZLIB
URL https://zlib.net/zlib-1.2.11.tar.gz
)
FetchContent_MakeAvailable(ZLIB)
add_executable(CMakeDemo main.cpp)
target_link_libraries(CMakeDemo ZLIB)
So when I execute from a build directory on Windows:
cmake ../
cmake --build .
Then zlib and my executable is built in Debug version and my executable is linked with that zlib Debug version.
But how to enhance the CMake to build my executable in Debug version but zlib in Release version and link my Debug executable with the zlib Release version? How to achieve that using FetchContent_Declare?
(I believe this has to be some common approach because for example when someone wants to use Google Test framework or zlib in a project then for sure he wants to use these external libraries in Release version always)

FetchContent() will integrate the dependency, here ZLIB, in your worktree like an add_subdirectory() so flags will be identical (if ZLIB is correctly configured to be use as subproject, spoiler: this is not the case you'll need to patch it...).
If you really want to build it in Release, you should try to use ExternalProject() and execute_process()
to build and install it at configure time then you can use find_package() to retrieve this pre-installed dependency.

I know that the question is old, but as I have been struggling a whole day with the same issue, and wanted to share my progress. Please bear in mind I'm a cmake newbie, and I'm not sure if what I'm doing conforms to best practices, and the example code I provide might have mistakes. It can also definitely be significantly improved - but I believe is sufficient to get someone off the ground, if they're having the same issue.
For my use case, I wanted the external libraries to be built at cmake configure time, instead of build time. To that effect, I used execute_process to configure and build the external libraries. This is the function I created for that effect:
function(fetch_and_build_lib)
set(options "")
set(oneValueArgs LIB_NAME GIT_REPOSITORY GIT_TAG PACKAGE_NAME)
set(multiValueArgs CONFIG_ARGS CONFIG_TYPES BUILD_ARGS COMPONENTS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (NOT DEFINED ARG_LIB_NAME)
message(FATAL_ERROR "fetch_and_build_lib called without LIB_NAME")
endif()
if (NOT DEFINED ARG_GIT_REPOSITORY)
message(FATAL_ERROR "fetch_and_build_lib called without GIT_REPOSITORY")
endif()
if (NOT DEFINED ARG_GIT_TAG)
message(FATAL_ERROR "fetch_and_build_lib called without GIT_TAG")
endif()
if (DEFINED ARG_PACKAGE_NAME)
set(PACKAGE_NAME ${ARG_PACKAGE_NAME})
else()
set(PACKAGE_NAME ${ARG_LIB_NAME})
endif()
include(FetchContent)
FetchContent_Declare(${ARG_LIB_NAME}
GIT_REPOSITORY "${ARG_GIT_REPOSITORY}"
GIT_TAG "${ARG_GIT_TAG}"
)
FetchContent_GetProperties(${ARG_LIB_NAME}
POPULATED LIB_POPULATED
)
message(STATUS "Checking if ${ARG_LIB_NAME} is populated: ${LIB_POPULATED}")
if (NOT LIB_POPULATED})
message(STATUS "Populating ${ARG_LIB_NAME}...")
FetchContent_Populate(${ARG_LIB_NAME})
set(LIB_BINARY_DIR ${${ARG_LIB_NAME}_BINARY_DIR})
set(LIB_SOURCE_DIR ${${ARG_LIB_NAME}_SOURCE_DIR})
message(STATUS "Configuring ${ARG_LIB_NAME}...")
execute_process(
COMMAND ${CMAKE_COMMAND}
"-S ${LIB_SOURCE_DIR}"
"-B ${LIB_BINARY_DIR}"
"-DCMAKE_GENERATOR=${CMAKE_GENERATOR}"
"-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}"
${ARG_CONFIG_ARGS}
WORKING_DIRECTORY "${LIB_BINARY_DIR}"
COMMAND_ECHO STDOUT
RESULT_VARIABLE result_config
)
if(result_config)
message(FATAL_ERROR "Failed to config ${ARG_LIB_NAME} exited with result: ${result_config}")
else()
message(STATUS "${ARG_LIB_NAME} configuring complete!")
endif()
foreach(config_type IN LISTS ARG_CONFIG_TYPES)
set(${ARG_LIB_NAME}_CONFIG_TYPE "${config_type}" CACHE INTERNAL "Config/build type for ${ARG_LIB_NAME}")
message(STATUS "Building ${ARG_LIB_NAME}... with CONFIG: ${config_type}")
execute_process(
COMMAND ${CMAKE_COMMAND}
--build "${LIB_BINARY_DIR}"
--config "${config_type}"
WORKING_DIRECTORY "${LIB_BINARY_DIR}"
COMMAND_ECHO STDOUT
${ARG_BUILD_ARGS}
RESULT_VARIABLE result_build
)
endforeach()
if(result_build)
message(FATAL_ERROR "Failed to build ${ARG_LIB_NAME} with result: ${result_build}")
else()
message(STATUS "${ARG_LIB_NAME} build complete!")
endif()
endif()
if (DEFINED ARG_COMPONENTS)
find_package(${PACKAGE_NAME} REQUIRED
COMPONENTS ${ARG_COMPONENTS}
PATHS ${LIB_BINARY_DIR}
NO_DEFAULT_PATH
)
else()
find_package(${PACKAGE_NAME} REQUIRED
PATHS ${LIB_BINARY_DIR}
NO_DEFAULT_PATH
)
endif()
endfunction()
What it does is as follows:
Downloads from git a repository with a specific tag
Configures the build system of the downloaded library
Builds the library as part of the main project configure stage
Makes sure that FindPackage can discover the newly built library
Then, in my CMakeLists.txt for my project, I use it like so:
add_executable(main main.cpp)
fetch_and_build_lib(
LIB_NAME "mylib"
GIT_REPOSITORY "https://github.com/myRepo/myLib.git"
GIT_TAG "1.2.3"
CONFIG_ARGS "-DMYLIB_DOC=OFF;-DMYLIB_TEST=OFF"
CONFIG_TYPES "debug, release"
PACKAGE_NAME "MyLIB"
)
find_package(MyLIB REQUIRED)
#Use Release version of library for Debug, MinSizeRel, RelWithDebInfo build types:
set_target_properties(MyLIB PROPERTIES
MAP_IMPORTED_CONFIG_MINSIZEREL Release
MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
MAP_IMPORTED_CONFIG_DEBUG Release
)
target_link_libraries(main myLib::myLib)
This invokes the previously defined function that downloads, configures and builds the library. It assumes that the library correctly exports its targets, and then remaps the imported configs to the current project config.
This is what actually ends up linking the debug lib with the release executable.
CMake documentation for MAP_IMPORTED_CONFIG_
Mandatory disclaimer:
Mixing release libs and debug executables is filled with pitfalls, both for shared and static libraries. I'm not discussing whether this is a good idea or not, or what the pitfalls are. Be sure you are aware of the limitations and what is safe and what is not safe to do when doing such a mix.

Related

CMake Linking Header Only Library (Working Example) for which I want explanation

I am learning how to use ExternalProject to download header only libaries and link to my executable.
My workflow is following:
I download header library Eigen using ExtenalProject:
cmake -DGET_LIBS=ON -DBUILD_SHARED_LIBS=ON -DBUILD_MY_PROJECTS=OFF -G
"Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Then I run the same CMakeLists a second time, but this time I disable ExternalProject and compile the executable that links the Eigen:
cmake -DGET_LIBS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_MY_PROJECTS=ON
-G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Question
Why I need to use both of these commands since in target_include_directories command I specify the same path as in include_directories?
In the code below I need two commands include_directories and target_include_directories.
I thought that it would be enough to use only target_include_directories, but without include_directories it wont work.
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
My full CMakeLists.txt is following:
project(superbuild LANGUAGES CXX)
cmake_minimum_required(VERSION 3.19)
########################################################################
# EIGEN
########################################################################
SET(GET_LIBS "" CACHE STRING "Set option to download dependencies")
if (GET_LIBS)
message(AUTHOR_WARNING ${GET_LIBS})
ExternalProject_Add(eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/eigen #this does nothing...
SOURCE_DIR "${CMAKE_BINARY_DIR}/install/eigen" #install in my local build dir
#SOURCE_DIR ${CMAKE_INSTALL_PREFIX}
BUILD_COMMAND "" #do not build
INSTALL_COMMAND "" #do not install
)
endif ()
###############################################################################
#EXECUTABLES
###############################################################################
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
Tl;dr never use include_directories. Ever. Learn how property visibility works in CMake instead.
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
In the code you supplied, include_directories is implicitly setting the INCLUDE_DIRECTORIES property on your my_exe target. Then the second line sets the INTERFACE_INCLUDE_DIRECTORIES property.
INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building the target. INTERFACE_INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building targets that link to this one. This doesn't make much sense for an executable
Slightly better would be to write:
target_include_directories(my_exe PRIVATE "${CMAKE_BINARY_DIR}/install/eigen/")
This will just populate INCLUDE_DIRECTORIES without touching the INTERFACE_ version. If you wanted both you could write PUBLIC instead of PRIVATE or INTERFACE. By definition, PUBLIC is just both of the others.
But this isn't a great dependency management strategy anyway... digging into the source tree guts of a project isn't scalable. It's also difficult to try different versions of Eigen without editing your build files.
I would just write:
cmake_minimum_required(VERSION 3.23)
project(example)
find_package(Eigen3 REQUIRED)
add_executable(my_exe main.cpp)
target_link_libraries(my_exe PRIVATE Eigen3::Eigen)
Use a proper package manager like vcpkg or Conan to handle downloading Eigen when it isn't available on the system.

FreeType2 Cross-compiling for Android

I was able to compile Freetype2 library for arm, arm64, x86, x86_64 architectures. These are steps I done to compile it.
Created standalone toolchains for architectures mentioned above.
make_standalone_toolchain.py \
--arch arm64 \
--api 26 \
--stl=libc++ \
--install-dir=my-arm64-toolchain
Set some envirement variables
export PATH=$PATH:`pwd`/my-toolchain/bin
target_host=aarch64-linux-android
export AR=$target_host-ar
export AS=$target_host-as
export CC=$target_host-gcc
export CXX=$target_host-g++
export LD=$target_host-ld
export STRIP=$target_host-strip
export CFLAGS="-fPIE -fPIC"
export LDFLAGS="-pie"
Configure freetype for compilation
./configure --host=aarch64-linux-android --prefix=/home/freetype-arm64 --without-zlib --without-harfbuzz --with-png=no
And finally make && install
Compilation was successfull and I was able to get Static freetype library.
I added library to my android studio libs folder.
This is folder structure for library:
libs---freetype
|---${ANDROID_ABI}
|---include
|---freetype2
|---freetype
---|Bunch of header files
|---ftbuild.h
|---lib
|---libfreetype.a
This is my CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
add_definitions("-DGLM_FORCE_SIZE_T_LENGTH -DGLM_FORCE_RADIANS")
add_subdirectory(src/main/cpp/glm)
add_library(freetype STATIC IMPORTED)
set_target_properties(freetype PROPERTIES IMPORTED_LOCATION${PROJECT_SOURCE_DIR}/libs/freetype/${ANDROID_ABI}/lib/libfreetype.a)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall")
add_library(native-lib SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/graph.cpp
src/main/cpp/text.cpp
src/main/cpp/graphDataWorker.cpp
)
target_include_directories(native-lib PRIVATE ${PROJECT_SOURCE_DIR}/libs/freetype/${ANDROID_ABI}/include/freetype2)
target_link_libraries(native-lib
android
freetype
GLESv2
EGL
glm
atomic
log
OpenSLES
)
The problem is that I can use library inside my code and android studio is not giving me any errors, but then i try to run application it gives me this error:
linker command failed with exit code 1 (use -v to see invocation)
PS: I Can't see freetype headers in android studio file tree aswell, but can see other libraries.
Question: Am I importing library in a wrong way or this is cross-compilation issue and if so how to do it properly?
I done a lot of research on this issue and was unable to find a solution
Any help will be appreciated.
Unless you mixed paths when installing your build of FreeType2, it seems that the compilation of the library is incorrect, since the linker complains about the library being for x86_64 architecture.
On Linux, usually you can check the architecture the library is built for with command file. Another way to check the architecture is to use the objdump tool that is shipped with Android NDK (if I remember correctly it is generated with a toolchain as well). Its name follows the same pattern than the compiler/linker/... tools, $target_host-objdump.
I would recommend to compile FreeType2 using CMake instead of autotools, at least in my case I successfully did it that way.
Here is a sample of CMake configuration I wrote to cross-compile FreeType2 for Android. It is a little complex because it is part of a project where I build several libraries.
The idea is that my CMake configuration uses ExternalProject to download and compile Freetype. My top-level CMakeLists.txt contains:
# CMakeLists.txt
set(DEVENV_CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}
-DCMAKE_C_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_C_FLAGS_${BUILD_TYPE}=${CMAKE_C_FLAGS_${BUILD_TYPE}}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_CXX_FLAGS_${BUILD_TYPE}=${CMAKE_CXX_FLAGS_${BUILD_TYPE}}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
)
if(BUILD_SHARED_LIBS)
list(APPEND DEVENV_CMAKE_ARGS -DBUILD_STATIC_LIBS=OFF -DLIBTYPE=SHARED)
else()
list(APPEND DEVENV_CMAKE_ARGS -DBUILD_STATIC_LIBS=ON -DLIBTYPE=STATIC)
endif()
if(DEFINED ANDROID_TOOLCHAIN_DIR)
get_filename_component(_toolchain_dir ${ANDROID_TOOLCHAIN_DIR} ABSOLUTE)
list(APPEND DEVENV_CMAKE_ARGS -DANDROID_TOOLCHAIN_DIR=${_toolchain_dir})
endif()
if(DEFINED CMAKE_ANDROID_ARCH_ABI)
list(APPEND DEVENV_CMAKE_ARGS -DCMAKE_ANDROID_ARCH_ABI=${CMAKE_ANDROID_ARCH_ABI})
endif()
if(CMAKE_TOOLCHAIN_FILE)
get_filename_component(_toolchain_file ${CMAKE_TOOLCHAIN_FILE} ABSOLUTE)
list(APPEND DEVENV_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${_toolchain_file})
endif()
add_subdirectory(freetype)
So the variable DEVENV_CMAKE_ARGS contains appropriate CMake arguments for cross-compiling that reflect my current CMake configuration. Under directory freetype, the CMakeLists.txt contains:
# freetype/CMakeLists.txt
project(freetype)
include(ExternalProject)
if(NOT FREETYPE_GIT_REPOSITORY)
set(FREETYPE_GIT_REPOSITORY "https://git.savannah.gnu.org/git/freetype/freetype2.git")
endif()
set(FREETYPE_GIT_REPOSITORY "${FREETYPE_GIT_REPOSITORY}"
CACHE STRING "Git repository for library Freetype."
)
if(NOT FREETYPE_GIT_TAG)
set(FREETYPE_GIT_TAG "VER-2-9")
endif()
set(FREETYPE_GIT_TAG "${FREETYPE_GIT_TAG}"
CACHE STRING "Tag or branch of Git repository to build."
)
message(STATUS "Freetype Git repository: ${FREETYPE_GIT_REPOSITORY}")
message(STATUS "Freetype Git tag: ${FREETYPE_GIT_TAG}")
externalproject_add(freetype
GIT_REPOSITORY "${FREETYPE_GIT_REPOSITORY}"
GIT_TAG "${FREETYPE_GIT_TAG}"
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/freetype/src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/freetype/build"
CMAKE_ARGS ${DEVENV_CMAKE_ARGS}
)
Basically this CMakeLists.txt uses ExternalProject to download and compile FreeType2 in the build directory.
And I use a toolchain file for Android:
# android.cmake
set(CMAKE_SYSTEM_NAME "Android")
set(CMAKE_ANDROID_STANDALONE_TOOLCHAIN ${ANDROID_TOOLCHAIN_DIR})
Sorry if it is overly complex; it comes from a project where I was playing with cross-compiling for Android. I hope it helps anyway.

CMake link to dynamic library generated by call to ExternalProject_Add

I have a small test program that I want to link to GLFW. I am currently able to download, configure and build the .dll using ExternalProject_Add command. When I build my test program I get an executable that doesn't run because it can't find the .dll. If I manually copy the .dll to the directory where the executable is, it runs just fine.
How do I get my executable to properly link to the library?
Is there a way to automatically copy the .dll to where it needs to be?
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-test)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
# Include OpenGL
find_package(OpenGL REQUIRED)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
link_libraries(${OPENGL_LIBRARIES})
endif()
# Add directories for library linkage
set(GLFW_LIB_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX/src)
link_directories(${GLFW_LIB_DIR})
# Download and unpack dependencies at configure time
configure_file(deps-CMakeLists.txt downloads/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
execute_process(COMMAND ${CMAKE_COMMAND} --build .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
add_subdirectory(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX
${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX
EXCLUDE_FROM_ALL )
include_directories(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX/include)
add_executable(GLFW-test src/GLFW-test.cpp)
target_link_libraries (GLFW-test glfw3 ${OPENGL_LIBRARIES})
add_custom_command(TARGET GLFW-test POST_BUILD # Adds a post-build event to MyTest
COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..."
"${GLFW_LIB_DIR}/glfw3.dll" # <--this is in-file
$<TARGET_FILE_DIR:GLFW-test>) # <--this is out-file path
dep-CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-dl)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE "./deps/")
# Include GLFW
ExternalProject_Add (
GLFW_EX
GIT_REPOSITORY "https://github.com/glfw/glfw.git"
GIT_TAG "master"
CMAKE_ARGS -DGLFW_BUILD_EXAMPLES=OFF
-DGLFW_BUILD_TESTS=OFF
-DGLFW_BUILD_DOCS=OFF
-DGLFW_INSTALL=OFF
-DBUILD_SHARED_LIBS=ON
UPDATE_COMMAND ""
TEST_COMMAND "" )
UPDATE:
The way I am using ExternalProject_Add is described on this site: https://crascit.com/2015/07/25/cmake-gtest/
It allows the external projects to be configured and built only once during the configure phase of my project. I have changed the directories around a bit from their test program to make things a little easier for when I eventually add more external projects. The test project on the site does not seem to account for dynamic libraries which is what I am trying to do.
UPDATE 2:
I've added 2 set commands to help clean up the build directory towards the top of the CMakeLists file. I also added a command at the bottom which copies .dll that is built from the ExternalProject_Add command to where I need it (next to the final executable). That seems to work for Windows, but it seems a bit hacky and doesn't resolve the errors in my IDE, which is currently Eclipse. Is there still a better way to do this?
Helpful Related topics:
Setting the RPATH for external projects?
Cmake on Windows doesn't add shared library paths (works on linux)
How to copy DLL files into the same folder as the executable using CMake?
How do I get my executable to properly link to the library?
As your second link states, there is no other way than to have .dll in the same directory as executable.
Is there a way to automatically copy the .dll to where it needs to be?
In you main project you already use variable CMAKE_RUNTIME_OUTPUT_DIRECTORY for setup directory where executables and .dlls should be placed after build. You can pass this variable to ExternalProject_add for force it to use same conventions:
ExternalProject_Add (...
CMAKE_ARGS -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
...
)
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
Packaging just uses install-tree of your project. So it is sufficient to install executables and libraries into same location:
set(INSTALL_RUNTIME_DIR bin)
install(TARGETS GLFW-test
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR}
)
install(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/glfw3.dll
DESTINATION ${INSTALL_RUNTIME_DIR}
)
Note, that target GLFW_EX obtained from external project has no special type(like executable or library), so you need to install its deliverables using plain filenames.

CMake third party library undefined reference

I already read and searched a lot (e.g. 1 2 3, several docs for CMake, similar projects, etc. to find a solution but I have not been able to solve my problem. I am relatively new to Cmake and Linux (Ubuntu 14.04).
I want to use libsbp (https://github.com/swift-nav/libsbp) to write a program in C++ to communicate with a GPS module. I cloned the repository and installed the C-Library. So now in /usr/local/lib there are two files: libsbp.so and libsbp-static.a and the headers are in /usr/local/include/libsbp
In my own project I include the headers with #include "libsbp/sbp.h" which also works.
Now the Problem: if I want to use a method from libsbp e.g. sbp_state_init(&s); I get undefined reference to "sbp_state_init(sbp_state_t*)"
The relevant part of my Cmake for my own project:
link_directories(/usr/local/lib)
add_executable(main ${QT_SOURCES} ${QT_HEADER_HPP})
target_link_libraries(main ${QT_LIBRARIES} ${catkin_LIBRARIES} sbp)
As I said before, I tried some things:
find_library(SBP_LIB sbp /usr/local/lib) -> same error
same goes for using libsbp in target_link_libraries or searching for it
link_directory(/usr/local/lib)
trying different paths, even moveing libsbp.so into the project directory and "finding" it with ${CMAKE_CURRENT_SOURCE_DIR}
Maybe you can help me!
edit:
this is the CMakeList.txt from the libsbp/c/src directory
if (NOT DEFINED BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS ON)
endif (NOT DEFINED BUILD_SHARED_LIBS)
file(GLOB libsbp_HEADERS "${PROJECT_SOURCE_DIR}/include/libsbp/*.h")
include_directories("${PROJECT_SOURCE_DIR}/CBLAS/include")
include_directories("${PROJECT_SOURCE_DIR}/clapack-3.2.1-CMAKE/INCLUDE")
include_directories("${PROJECT_SOURCE_DIR}/lapacke/include")
include_directories("${PROJECT_SOURCE_DIR}/include/libsbp")
set(libsbp_SRCS
edc.c
sbp.c
)
add_library(sbp-static STATIC ${libsbp_SRCS})
install(TARGETS sbp-static DESTINATION lib${LIB_SUFFIX})
if(BUILD_SHARED_LIBS)
add_library(sbp SHARED ${libsbp_SRCS})
install(TARGETS sbp DESTINATION lib${LIB_SUFFIX})
else(BUILD_SHARED_LIBS)
message(STATUS "Not building shared libraries")
endif(BUILD_SHARED_LIBS)
install(FILES ${libsbp_HEADERS} DESTINATION include/libsbp)
this is the CMakeList.txt from /libsbp/c/
cmake_minimum_required(VERSION 2.8.9)
project(libsbp)
# Setup flags for Code Coverage build mode
set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage" CACHE STRING
"Flags used by the C++ compiler for building with code coverage."
FORCE )
set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} --coverage" CACHE STRING
"Flags used by the C compiler for building with code coverage."
FORCE )
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
"${CMAKE_EXE_LINKER_FLAGS_DEBUG} --coverage" CACHE STRING
"Flags used for linking binaries with code coverage."
FORCE )
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
"${CMAKE_SHARED_LINKER_FLAGS_DEBUG} --coverage" CACHE STRING
"Flags used by the shared libraries linker during builds with code coverage."
FORCE )
mark_as_advanced(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
# Update the documentation string of CMAKE_BUILD_TYPE for GUIs
set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage."
FORCE )
# Set project version using Git tag and hash.
execute_process(
COMMAND git describe --dirty --tags --always
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE GIT_VERSION_FOUND
ERROR_QUIET
OUTPUT_VARIABLE GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (GIT_VERSION_FOUND)
set(VERSION "unknown")
else (GIT_VERSION_FOUND)
set(VERSION ${GIT_VERSION})
endif (GIT_VERSION_FOUND)
# Set project version explicitly for release tarballs.
#set(VERSION foo)
message(STATUS "libsbp version: ${VERSION}")
cmake_minimum_required(VERSION 2.8)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Some compiler options used globally
set(CMAKE_C_FLAGS "-Wall -Wextra -Wno-strict-prototypes -Wno-unknown-warning-option -Werror -std=gnu99 ${CMAKE_C_FLAGS}")
add_subdirectory(src)
add_subdirectory(docs)
add_subdirectory(test)
It seems that your program uses C++ and the library is written in C.
Symbols in C and C++ are encoded differently (mangled). When including C headers from C++ you need to tell the compiler. This can be done by declaring the symbols extern "C".
extern "C" {
#include <libsbp/sbp.h>
}
Some libraries already include this in their headers, but not sbp.
You have (at least) two possibilities:
Installing the library (this is what you did)
Integrating the library in your CMake project
When installing the library, the target_link_libraries command needs to be modified slightly:
find_library(SBP_LIB sbp /usr/local/lib)
target_link_libraries(main ${QT_LIBRARIES} ${catkin_LIBRARIES} ${SBP_LIB})
When you integrate the library in your CMake project, you can directly use the following command without using find_library. This works, because the library is known to CMake since it is built within the current project.
target_link_libraries(main ${QT_LIBRARIES} ${catkin_LIBRARIES} sbp)

CMakeLists configuration to link two C++ projects

I have the following situation: a Project A depends on a Project B, but both are built at the same time. Project A has to include the includes of project B and it needs also to link its libraries. Up to now I've tried this way:
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/other_project other_project)
and then:
INCLUDE_DIRECTORIES(includ ${CMAKE_SOURCE_DIR}/other_project/include})
LIST(APPEND LINK_LIBS other_project)
in the CMakeLists.txt of Project A
but it doesn't seem to work, the compiler also gives me error when including the headers of Project B saying that they do not exist.
What is the right way to add dependencies in A? How should the CMakeLists.txt look like?
EDIT:
as suggested in the comments, this question was addressed in this, however I'd like to see an example of how to use it in a CMakeList.txt file.
The following is a simple example which builds zlib and then builds libxml2 which depends on the zlib we build. One thing to note, I quickly pulled this example from stuff I've done before. The libxml2 example uses make, thus will only actually build on a system which has it, e.g. Linux, Mac ...
Here is the proposed directory structure for this example ...
src/
-- CMakeLists.txt
CMake/
---- External_ZLib.cmake
---- External_libxml2.cmake
Dowloads/ ( no need to create this directory, CMake will do it for you )
build/ ( Out of source build to prevent littering source tree )
Files:
CMakeLists.txt
# Any version that supports ExternalProject should do
cmake_minimum_required( VERSION 3.1)
project(test_ext_proj)
set(test_ext_proj_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
set(CMAKE_MODULE_PATH ${test_ext_proj_CMAKE_DIR} ${CMAKE_MODULE_PATH})
set(test_ext_proj_BUILD_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install)
set(test_ext_proj_DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Downloads CACHE PATH "Directory to store downloaded tarballs.")
include(ExternalProject)
include(External_ZLib)
include(External_libxml2)
External_ZLib.cmake:
set(ZLib_version 1.2.8)
set(ZLib_url "http://zlib.net/zlib-${ZLib_version}.tar.gz")
set(ZLib_md5 "44d667c142d7cda120332623eab69f40")
ExternalProject_Add(ZLib
URL ${ZLib_url}
URL_MD5 ${ZLib_md5}
PREFIX ${vision-tpl_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
CMAKE_GENERATOR ${gen}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${test_ext_proj_BUILD_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
)
#This variable is required so other packages can find it.
set(ZLIB_ROOT ${test_ext_proj_BUILD_INSTALL_PREFIX} CACHE PATH "" FORCE)
External_libxml2.cmake:
set(libxml2_release "2.9")
set(libxml2_patch_version 0)
set(libxml2_url "ftp://xmlsoft.org/libxml2/libxml2-sources-${libxml2_release}.${libxml2_patch_version}.tar.gz")
set(libxml2_md5 "7da7af8f62e111497d5a2b61d01bd811")
#We need to state that we're dependent on ZLib so build order is correct
set(_XML2_DEPENDS ZLib)
#This build requires make, ensure we have it, or error out.
if(CMAKE_GENERATOR MATCHES ".*Makefiles")
set(MAKE_EXECUTABLE "$(MAKE)")
else()
find_program(MAKE_EXECUTABLE make)
if(NOT MAKE_EXECUTABLE)
message(FATAL_ERROR "Could not find 'make', required to build libxml2.")
endif()
endif()
ExternalProject_Add(libxml2
DEPENDS ${_XML2_DEPENDS}
URL ${libxml2_url}
URL_MD5 ${libxml2_md5}
PREFIX ${test_ext_proj_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure
--prefix=${test_ext_proj_BUILD_INSTALL_PREFIX}
--with-zlib=${ZLIB_ROOT}
BUILD_COMMAND ${MAKE_EXECUTABLE}
INSTALL_COMMAND ${MAKE_EXECUTABLE} install
)