CMake combine multiple precompiled lib files to single target - c++

We are using cmake and started using conan as a package manager.
Before conan however prebuild 3rd Party libs like GTest where added via
find_package(GTest REQUIRED). (By adding custom find Scripts for each library find_package() was able to locate our prebuild third parties)
In the new conan way it's pretty easy to add 3rd parties to a library
add_library(myLib sources)
target_link_libraries(myLib PRIVATE GTEST::GTEST)
To achieve backwards compatibility i need to create a target with the name GTest::GTest from the find_package().
Now the question is how can i create a new target from the find_package() result?
I did try to create a new dummy target
find_package(GTest REQUIRED)
add_library("GTest" "foo.cpp")
target_link_libraries("GTest" PUBLIC "${GTest_LIBRARIES}")
add_library("GTEST::GTEST" ALIAS GTest)
where foo.cpp is just an empty file.
when linking against the target GTEST::GTEST
this results in a linker error GTEST_1_0d.lib was not found
How would you create a cmake target from prebuild binaries?
Could you help me out here?
Edit: Sorry for beeing unclear.
I do not want to touch legacy systems. Therefore Conan is not available on legacy systems.
The question is: How can I create targets from precompiled libraries that look exactly like Conan targets

To achieve backwards compatibility i need to create a target with the name GTest::GTest from the find_package(). Now the question is how can i create a new target from the find_package() result?
As you say, the result of your call to find_package(GTest) is that GTEST::GTEST is created. So just write
add_library(GTest::GTest ALIAS GTEST::GTEST)
If you have code that needs to use the other name, no?
On the other hand:
find_package(GTest REQUIRED)
If the result of your call to find_package(GTest) is the ancient built-in CMake module, then something like this might work:
find_package(GTest REQUIRED)
# Not using Conan
if (NOT TARGET GTEST::GTEST)
if (TARGET GTest::gtest)
# CMake 3.20+ module in use
add_library(GTEST::GTEST ALIAS GTest::gtest)
elseif (TARGET GTest::GTest)
# CMake 3.5+ module in use
add_library(GTEST::GTEST ALIAS GTest::GTest)
else ()
add_library(GTEST::GTEST IMPORTED INTERFACE)
target_include_directories(GTEST::GTEST INTERFACE "${GTEST_INCLUDE_DIRS}")
target_link_libraries(GTEST::GTEST INTERFACE "${GTEST_LIBRARIES}")
endif ()
endif ()
You can decide whether to add GTest::gtest_main, GTest::Main, or use ${GTEST_BOTH_LIBRARIES} in each of the three respective branches.

Related

Exporting and packaging prebuilt libraries in cmake

instead of asking a question directly, I'll expose my use case and the way I tried (but failed) to solve it.
Say I have:
3 shared libraries A, B and C
A require B and C
A comes with a set of headers
That's it, no extra information, it's provided by a vendor and not possibly subject to any change (modernization/cmake packages, etc).
A should always be packaged with B and C. I should only need to link with A and cmake should transitively link with B and C.
Now, I'd like to make it more "modern cmake" friendly and by able to:
First usecase: Create a repo containing these libs and calling add_subdirectory() from a parent project.
First usecase: Create a package (say debian pkg . deb) containing the relevant AConfig.cmake AConfigVersion.cmake and ATargets.cmake. Then a simple system install of the pkg and a find_package() should to the trick.
What has been done:
I tried using INTERFACE IMPORTED library and INTERFACE.
Because I want to support packaging the libs INTERFACE IMPORTED can't be used (you can't install it as far as I know/tested).
INTERFACE is working fine for the first usecase, using add_subdirectory(), headers are found, everything links, but because the user may not have at this point, the shared lib in is path, he can't run the tests for instance.
Then comes the export part needed to make the shared libs available and to make find_package() work. I succeed to export/package the libs A B C, the headers for A and the files needed for find_package().
But when in a client library D, find_package(A REQUIRED) finds the lib (no messages such as "Could not find a package configuration file provided by "A" ") it doess NOT create any target I can link on. I took a look at the generated ATargets.cmake:
# generated stuff before
add_library(A::A INTERFACE IMPORTED) # This is cannot be used at all, does not create a target I can link on unless I remove IMPORTED
set_target_properties(A::A PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" # that's fine
INTERFACE_LINK_LIBRARIES "A.so;B.so.1.0;C.so.1.0" # that's not fine I need ${_IMPORT_PREFIX}/${CMAKE_INSTALL_LIBDIR}/ before each libs like it did for the headers
)
# generated stuff after
Note that if I remove the IMPORTED in the add_library of the ATargets.cmake and remove the namespace stuff, the target A is correctly accessible in any client cmake project using find_package but it'll NOT link correctly probably because A.so (and B and C) is not referenced using ${_IMPORT_PREFIX}/lib/A.so. I tried adding ${_IMPORT_PREFIX}/lib/ before all libs in INTERFACE_LINK_LIBRARIES, removed the IMPORTED keyword and namespace stuff and guess what, it works perfectly... Now, how do I do that without the trick.
The content of AConfig.cmake.in is:
#PACKAGE_INIT#
include(CMakeFindDependencyMacro)
# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/ATargets.cmake")
# check_required_components(#PROJECT_NAME#) # It is commented because unless I apply the fix specified earlier (IMPORTED and namespace removed), during the find_package call I get:
#################
# CMake Error at /usr/lib/cmake/A/AConfig.cmake:8 # (check_required_components):
# Unknown CMake command "check_required_components".
#################
That's pretty much it, the rest of this post is implementation details. This code below can be used to solve use case 1 but the export package and cmake config/target files are not correct.
add_library(A
INTERFACE)
add_library(A::A ALIAS A)
target_include_directories(A
INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>")
target_link_libraries(A
INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/A.so>"
"$<INSTALL_INTERFACE:A.so>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/B.so>"
"$<INSTALL_INTERFACE:B.so>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/C.so>"
"$<INSTALL_INTERFACE:C.so>")
#### install
install(TARGETS A
EXPORT ATargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT A_Runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Runtime NAMELINK_COMPONENT A_Development
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Development)
install(DIRECTORY "lib/"
DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include"
DESTINATION "include")
write_basic_package_version_file(AConfigVersion.cmake
VERSION "${PACKAGE_VERSION}"
COMPATIBILITY SameMajorVersion)
install(EXPORT ATargets
FILE ATargets.cmake
NAMESPACE A::
DESTINATION "lib/cmake/A")
configure_file(AConfig.cmake.in AConfig.cmake #ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/AConfigVersion.cmake"
DESTINATION "lib/cmake/A")
# Then some cpack stuff that is not affecting the work done earlier
In a client lib/cmake project after the installation of the generated package (generated by cpack):
find_package(A)
target_link_libraries(my_project PUBLIC/PRIVATE A::A) # Should bring in the headers and link with A B and C
Documentation:
cmake: create a new library target which consists of a prebuilt library
https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/Exporting-and-Importing-Targets
Possible to add an imported library to target_link_libraries that takes care of include directories too?
Exporting an imported library
https://discourse.cmake.org/t/how-to-control-import-prefix-in-exported-targets-cmake-list-file-generated-by-cmake/2291
Create relocatable package with proper autogenerated config cmake
https://discourse.cmake.org/t/exporting-packages-with-a-custom-find-module/3820/2
Thanks for reading/helping

How to import package in cmake from vcpkg?

When I vcpkg install simdjson , it returns :
The package simdjson:x64-linux provides CMake targets:
find_package(simdjson CONFIG REQUIRED)
target_link_libraries(main PRIVATE simdjson::simdjson simdjson::simdjson-flags simdjson::simdjson-headers)
So I add
find_package(simdjson CONFIG REQUIRED)
target_link_libraries(main PRIVATE simdjson::simdjson simdjson::simdjson-flags simdjson::simdjson-headers)
to CMakeLists.txt to use the package simdjson
But when I vcpkg install redis-plus-plus[cxx17] , it returns nothing . What should I do to let cmake use this package ?
Unfortunately, redis-plus-plus doesn't supply CMake config files. Someone should open an issue with upstream. It's honestly pretty unacceptable to not support find_package for your library. Thus, thanks to the authors' negligence, you will have to create an imported target for their library yourself. Here's an example CMakeLists.txt, step by step. We'll start with the standard boilerplate:
cmake_minimum_required(VERSION 3.19)
project(test-redis)
Then we need to find hiredis, which is one of Redis++'s dependencies:
find_package(hiredis REQUIRED)
This will create a target called hiredis::hiredis we'll link to later. Now we'll create a target to hold the Redis++ usage information.
add_library(redis++::redis++ UNKNOWN IMPORTED)
Now we need to actually find the header path and redis++ libraries:
find_path(REDIS_PP_HEADER sw REQUIRED)
find_library(REDIS_PP_LIB redis++ REQUIRED)
And now we can tell CMake that the target we just created manages the library we just found:
set_target_properties(redis++::redis++ PROPERTIES IMPORTED_LOCATION "${REDIS_PP_LIB}")
And finally we can set up the include paths and dependency on Hiredis.
target_include_directories(redis++::redis++ INTERFACE "${REDIS_PP_HEADER}")
target_link_libraries(redis++::redis++ INTERFACE hiredis::hiredis)
We're now ready to use the library like we ought to be able to expect to.
add_executable(main main.cpp)
target_link_libraries(main PRIVATE redis++::redis++)

Does the header would be included explicitly when use find_package in CMake?

When I use PyTorch for C++, it's pretty easy to just use find_package to set up the dependency. And here is the CMakeLists.txt:
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(dcgan)
set(CMAKE_PREFIX_PATH /User/root/libtorch) # I added this line, does it effect?
find_package(Torch REQUIRED)
add_executable(dcgan dcgan.cpp)
target_link_libraries(dcgan "${TORCH_LIBRARIES}")
set_property(TARGET dcgan PROPERTY CXX_STANDARD 14)
There isn't any explicit command to include the header, but the header could be found if target_link_libraries(dcgan "${TORCH_LIBRARIES}") exists. I am curious why the header file could be found even there is no target_include_directories(dcgan PUBLIC ${TORCH_INCLUDE_DIRS}).
The code is on the official website of PyTorch and it works on MacOS and Linux. What happened.
ADD:
The package is in a directory where the compiler knows nothing about it.
The include path can be set as propagated setting in the dependency:
target_link_libraries
Specify libraries or flags to use when linking a given target and/or
its dependents. Usage requirements from linked library targets will be
propagated. Usage requirements of a target’s dependencies affect
compilation of its own sources.
https://cmake.org/cmake/help/latest/command/target_link_libraries.html
That means that target_link_libraries will configure the target . It will set target_compile_features, target_compile_options, target_compile_directories, if they're set as INTERFACE or PUBLIC in the dependency.
E.g.
add_library(Lib ${SRCS_LIB})
target_include_directories(Lib INTERFACE ${DIRECTORY})
add_exectuable(Exe ${SRCS_EXE})
target_link_libraries(Exe PRIVATE Lib)
In this example Exe will inherit the include directories from Lib. You don't need to set them explicitly.
That's also how Conan works, e.g. Getting started
cmake_minimum_required(VERSION 2.8.12)
project(MD5Encrypter)
add_definitions("-std=c++11")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
add_executable(md5 md5.cpp)
target_link_libraries(md5 ${CONAN_LIBS})
and how it's described in Effective Modern CMake
Use exported targets of external packages.
Don’t fall back to the old
CMake style of using variables defined by external packages. Use the
exported targets via target_link_libraries instead.
Best practice is to not use target_include_directories for your dependencies.

Using OpenCV's CMake module targets instead of including all libraries directly?

Problem: I have a project which uses OpenCV. I'm able to switch to what ever the latest version is if need be. My project uses CMake. I currently integrate opencv like so:
# OPENCV package
find_package(OpenCV)
add_library(opencv INTERFACE)
target_include_directories(opencv
INTERFACE
${OpenCV_INCLUDE_DIRS})
target_link_libraries(opencv
INTERFACE
${OpenCV_LIBS})
add_executable(opencv_example example.cpp)
target_link_libraries(opencv_example
PRIVATE
opencv
)
I cannot find examples of OpenCV using explicit module dependency targets. In order to not cruff up global includes unessessarily or leave naked variables laying around. I create an interface target for OpenCV and use this interface target instead of doing what OpenCV CMake example recommends:
# Find OpenCV, you may need to set OpenCV_DIR variable
# to the absolute path to the directory containing OpenCVConfig.cmake file
# via the command line or GUI
find_package(OpenCV REQUIRED)
# Declare the executable target built from your sources
add_executable(opencv_example example.cpp)
# Link your application with OpenCV libraries
target_link_libraries(opencv_example ${OpenCV_LIBS})
My question is it possible to just use OpenCV's module targets instead of having to bringing the whole kitchen sink along?
example (see module list here):
...
add_executable(opencv_example example.cpp)
target_link_libraries(opencv_example
PRIVATE
opencv::core
opencv::video
opencv::imgproc
)
thus I only get dependencies required for the modules I actually use. I looked through the OpenCV repository, but it is full of custom cmake macros and functions and It is difficult to see where a target is declared, let alone if it can be accessed from find_package.
While opencv::module doesn't work, if you use find_package, starting from at most OpenCV3.2 (it may have been available before) you can use targets like opencv_module for example with:
find_package(OpenCV REQUIRED COMPONENTS core imgproc video)
add_executable(opencv_example example.cpp)
target_link_libraries(opencv_example
PRIVATE
opencv_core
opencv_video
opencv_imgproc
)
I found out these targets were exported here:https://github.com/opencv/opencv/issues/8028#issuecomment-273419374

CMake export package that relies on external library

I have a project written using C++ and CMake, using Boost, that I'm trying to make a standalone binary/header package for to allow other people to link against my work. I'm using cmake installers for this. However, I'm running into issues with install(EXPORTS ...) when my library links to an external library. In particular, the Boost library and header directory locations are hard-coded into the exported file, and I can't figure out how to make it work better.
Have an example. (Untested; if it's not clear I can elaborate or fix it.)
CMakeLists.txt:
package(MyLibrary)
set(MyLibrary_VERSION 1.0)
find_component(BOOST 1.55.0 REQUIRED COMPONENTS serialization)
set(INSTALL_INCLUDE_DIR "C:/MyLibrary/include")
set(INSTALL_SRC_DIR "C:/MyLibrary/include")
set(INSTALL_BIN_DIR "C:/MyLibrary/bin")
set(INSTALL_LIB_DIR "C:/MyLibrary/lib")
set(INSTALL_CMAKE_DIR "C:/MyLibrary/cmake")
set(HEADERS myfile.hpp)
set(SOURCES myfile.cpp)
install(FILES ${HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR} COMPONENT headers)
install(FILES ${SOURCES} DESTINATION ${INSTALL_SRC_DIR} COMPONENT sources)
add_library(MyLibrary STATIC
${HEADERS} ${SOURCES})
target_link_libraries(MyLibrary
${Boost_SERIALIZATION_LIBRARY})
target_include_directories(MyLibrary
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS}>"
PUBLIC "$<INSTALL_INTERFACE:include;${Boost_INCLUDE_DIRS}>")
install(TARGETS MyLibrary EXPORT MyLibrary-depends
DESTINATION ${INSTALL_LIB_DIR} COMPONENT libraries)
configure_package_config_file(MyLibraryConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
VERSION ${MyLibrary_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"
DESTINATION "${INSTALL_CMAKE_DIR}")
install(EXPORT MyLibrary-depends
FILE MyLibrary-depends.cmake
DESTINATION "${INSTALL_CMAKE_DIR}")
MyLibraryConfig.cmake.in
#PACKAGE_INIT#
if (NOT MyLibrary_FOUND)
set(MyLibrary_FOUND 1)
find_package(Boost 1.55.0 COMPONENTS SERIALIZATION)
include(MyLibrary-depends.cmake)
# random directory stuff, etc.
endif()
The issue is that MyProject-depends.cmake ends up with the value of ${Boost_INCLUDE_DIRS} and ${Boost_SERIALIZATION_LIBRARY}, which are both absolute paths and screw up the portability of the install.
I've tried a couple of things, none of which seem to fix all my problems.
target_include_directories:
I tried escaping the $, with the hope that MyProject-depends.cmake would pick up the value of the Boost_INCLUDE_DIRS variable on include-time:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include;\${Boost_INCLUDE_DIRS}>"
...)
But, of course, INSTALL_INTERFACE thinks that ${Boost_INCLUDE_DIRS} is a relative path and prefixes it wit {$_IMPORT_DIR} which breaks everything.
I can ditch the MyProject-depends.cmake route entirely, and add it into MyProjectConfig.cmake.in:
CMakeLists.txt:
target_include_directories(MyProject
PUBLIC "$<INSTALL_INTERFACE:include>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR};${Boost_INCLUDE_DIRS>")
and MyProjectConfig.cmake.in:
include(MyProject-depends.cmake)
set_target_properties(MyProject
INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}")
That option seems to work but is a pain.
target_link_libraries:
I'm having more trouble with the library linking. I tried the same trick, moving stuff into the MyProjectConfig.cmake.in file for more control, but
target_link_libraries(MyProject ${Boost_SERIALIZATION_LIBRARIES})
doesn't work on imported libraries, and
set_target_properties(MyProject INTERFACE_LINK_LIBRARIES ${Boost_SERIALIZATION_LIBRARY})
fails because ${Boost_SERIALIZATION_LIBRARY} expands to something like optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd and set_target_properties doesn't like the keywords.
Now I'm left with some sort of remapping using
"$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>"
but I'll also have to detect whether or not a debug library is specified... which is doable, but seems like yak shaving to me.
So, sages of the stack... any advice? Is there some obvious module or clever method that I'm overlooking?
(And thanks for making it all the way through!
Also: the cmake install(EXPORTS ...) documentation contains the helpful line "If a library target is included in the export but a target to which it links is not included the behavior is unspecified." Yeah, basically, I'm looking for a workaround.
I ended up with the last target_link_libraries answer, ditching the built-in import structure entirely and writing the CMake module to remap
optimized;C:/boost/stage/lib/boost_serialization.lib;debug;C:/boost/stage/lib/boost_serialization.libd
into
$<$<CONFIG:DEBUG>:${Boost_SERIALIZATION_LIBRARY_DEBUG}>$;<$<CONFIG:RELEASE>:${Boost_SERIALIZATION_LIBRARY_RELEASE}>
Not at all pretty, but it was the best I could come up with. So it goes.