In a project, we use ExternalProject_add to manage regular third parties and drive their download and build process (some come from regular URL, others from a git repository)
Recently, I came across the need to add an extra thirdparty. It is so far the only header-only thirdparty we would have. For those interested, it is kvasir_mpl.
However, when I try to target_link_libraries with kvasir_mpl, CMake always considers it as a regular library and in the end the build fails:
[ 83%] Linking CXX executable app
/usr/bin/ld: cannot find -lkvasir_mpl
I devised a minimal example to reproduce the problem:
./CMakeLists.txt:
cmake_minimum_required( VERSION 3.7.0 )
project( Test CXX )
add_subdirectory( kvasir )
add_subdirectory( app )
./app/CMakeLists.txt:
project( App CXX )
add_executable( app main.cpp )
target_link_libraries( app kvasir_mpl )
kvasirmpl/CMakeLists.txt:
cmake_minimum_required( VERSION 3.7.0 )
project( KvasirMpl )
include( ExternalProject )
ExternalProject_Add(
3rdparty_kvasirmpl
GIT_REPOSITORY https://github.com/kvasir-io/mpl
GIT_TAG origin/development
INSTALL_COMMAND ""
BUILD_COMMAND "" )
Note that if I use the keyword signature target_link_libraries( app INTERFACE kvasir_mpl ) my issue is resolved. However in our real use case, the target_link_libraries is run through custom CMake functions and can be passed anything from a regular library file to a CMake target forcing us to use the plain signature.
Is there a way to make the plain signature work in this case?
In kvasirmpl/CMakeLists.txt, add the following lines:
add_library(kvasir_mpl INTERFACE)
target_include_directories(kvasir_mpl PUBLIC <includedirs>)
set_target_properties(kvasir_mpl PROPERTIES LINKER_LANGUAGE CXX)
This tells CMake there is a library not built by CMake (Interface), what the include directories are when linking with that library... You could also add extra compile flags etc.
The linker language must be specified because CMake wishes to know whether it's C or C++, and won't deduct that in a header-only library.
Related
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.
I'm creating a native library under Android Studio, and I'm hurting the following problem.
In my AndroidStudio project CMakeLists.txt, I have this:
cmake_minimum_required(VERSION 3.7.0)
add_library(native-lib
SHARED
src/main/cpp/native-lib.cpp )
IF(USE_EXTERNAL)
include(ExternalProject)
ExternalProject_Add(
project_mylib
DOWNLOAD_COMMAND ""
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/mylib/"
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}
)
add_dependencies(native-lib project_mylib)
ELSE()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/mylib/)
ENDIF()
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries(native-lib
${log-lib}
mylib
)
A self-made library is located in src/main/cpp/mylib/, with a CMakeLists.txt:
cmake_minimum_required(VERSION 3.7.0)
project(lib)
add_library(mylib SHARED lib.cpp)
INSTALL(TARGETS mylib
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
)
When I use the "traditional" add_subdirectory(...) everything goes well. But, if I use the ExternalProject_Add(...) version, linker is skipping the compiled libmylib.so library and so cannot link mylib to native-lib.
I have the following message: skipping incompatible /home/.../app/.externalNativeBuild/cmake/debug/arm64-v8a/lib/libmylib.so when searching for -lmylib
My guess is that all the flags set by AndroidStudio for the root CMakeLists.txt are not set when the ExternalProject is compile leading to an incompatible shared library.
So, I wonder if there is a way to compile a cmake ExternalProject like it was part of the root project (sharing the same compile flags etc) ?
Thanks for any advice
Perhaps I simply can't find it, but I want to add some code to a project of mine (libunwind found here http://www.nongnu.org/libunwind/download.html)
This library does not come with a CMakeLists.txt file and when I try to include it cmake complains about this fact. Right now I've simply added the libunwind directory to my external code and added a reference in my main CMakeLists.txt
Any input would be great.
Dealing with libraries there are 2 options for you :
If you've downloaded and was able to build and install it you can try to find it later on inside you CMAKE like this ( in case of Boost ) and link to your target:
find_package( Boost COMPONENTS date_time system serialization thread program_options filesystem unit_test_framework regex chrono REQUIRED )
if( NOT Boost_FOUND )
message( FATAL_ERROR "Cannot find boost!" )
endif( NOT Boost_FOUND )
message(STATUS "boost found")
include_directories( ${Boost_INCLUDE_DIRS} )
link_directories( ${Boost_LIBRARY_DIRS} )
target_link_libraries(YOUR_TARGET_NAME ${Boost_LIBRARIES})
2. You can add external library sources as a stand-alone target and use smth like this for CMake to build it :
set (sources
async_waiter.h
async_waiter_impl.h
async_waiter_impl.cpp
)
add_library( async_waiter ${sources} )
and later on link you target to it with :
target_link_libraries(YOUR_TARGET_NAME async_waiter)
If you want to build it each time along with your project, the easiest way would be to:
Add the source code somewhere into your project tree
Add a custom CMake target which should run before the compilation starts
In that custom target, run whatever is needed to compile the library (in your case it's ./configure -> make -> make install.
However that is rarely needed and most of the times you should just build the library once and link it as any other external library.
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.
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)