Exporting and packaging prebuilt libraries in cmake - c++

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

Related

Using 3rd Party Shared Object Library and header files in CMake

I am trying to use this ZED Open Capture library for using the ZED Mini camera for my project on RaspberryPi. I succesfully installed the library and the shared object file is at /usr/local/lib/libzed_open_capture.so and the include headers are at the location /usr/local/include/zed-open-capture/.
To include this library I am adding the following lines to my CMakeLists.txt
find_library(ZED_LIB zed_open_capture)
include_directories("/usr/local/include/zed-open-capture/")
add_executable(zed_pub src/zed_pub.cpp)
target_link_libraries(zed_pub ${ZED_LIB})
Now when I use this code , it shows this error "‘sl_oc::video’ has not been declared"
#include "videocapture.hpp" //Library Header File
sl_oc::video::VideoCapture cap;
cap.initializeVideo();
const sl_oc::video::Frame frame = cap.getLastFrame();
Can someone please explain me how a Shared Object Library file along with header files should be used in CMake? The library has already been installed using CMake build and sudo make install on my Linux system.
The github repo of the library is at https://github.com/stereolabs/zed-open-capture
Also I cannot find Find_PKG_name.cmake so I cannot use find_package() option.
videocapture.hpp wraps the definitions you need inside #ifdef VIDEO_MOD_AVAILABLE. It seems likely that this is not defined. The root CMakeLists.txt in the ZED package defaults BUILD_VIDEO to ON, so this was likely all defined for the package build. But as others have pointed out, the package does not persist this information anywhere in the installation. The "right" way for the package to do it would be EITHER to configure/modify the include files at install time to account for the build configuration, probably by generating a "config.hpp" file with the appropriate definitions. OR to include a zed-config.cmake file in the installation, with all the necessary imports and definitions.
Your short-circuit solution should be fine. Just add target_compile_definitions(zed_pub PUBLIC VIDEO_MOD_AVAILABLE). If you want to do it more cleanly for the future, create an IMPORTED target for zed_lib, and set both the include_directories and compile_definitions on that target, so that all users of the library get this defined automatically.
According to the ZED Open Capture's CMakeLists.txt:
...
# Install rules
set_target_properties(${PROJECT_NAME} PROPERTIES
PUBLIC_HEADER "${HDR_FULL}"
)
install(TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/zed-open-capture)
ref: https://github.com/stereolabs/zed-open-capture/blob/dfa0aee51ccd2297782230a05ca59e697df496b2/CMakeLists.txt#L142-L148
ZED Open Capture seems to not provide any "CMake config" file...
So you must create your own FindZED.cmake module and/or improve the CMakeLists.txt of this project...

Linking two separate cmake projects where one is dependant on the other

I have two separate projects, A and B. Both have large folder structures, lots of dependencies and both have their own CMakelists.txt structures as well. Neither depend on each other.
I am adding new functionality to project A that depends on a header from project B, along with the network of files in Project B that header depends on.
I am at a loss for the correct way to link these with cmake. I added to header as an include where necessary in my code, but am not sure the correct way to link it. Do I use target_include_directories and add the binaries from project B, or the source code, or both? Project A won't be building B as it is already set up, with the binaries and source in separate subdirectories. I just need A to be able to call the functionality from B.
I am not very familiar with cmake yet, so any pointers would be appreciated.
Thanks.
Project B should export its targets.
That way, in project A, you could do something like this:
find_package(B REQUIRED)
target_link_libraries(A PUBLIC B::B)
And when invoking CMake, add B's build directory in the prefix path:
# in A/build
cmake .. -DCMAKE_PREFIX_PATH=/path/to/B/build
But in order for that to work, you must change your B CMake files to export your targets (as it should):
include(GNUInstallDirs)
add_library(B INTERFACE)
add_library(B::B ALIAS B)
target_link_libraries(B PUBLIC dependency-of-B)
target_compile_features(B INTERFACE cxx_std_17) # or any language version
# Set B include directory. Will add include directory for both B and A
target_include_directories(B PUBLIC
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/your-include-dir>
)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/B-config.cmake" DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
install(TARGETS B EXPORT B-targets)
# install package
install(EXPORT B-targets
FILE B-targets.cmake
NAMESPACE B::
DESTINATION share/cmake/B
)
# export build tree
export(
EXPORT B-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/B-targets.cmake"
NAMESPACE B::
)
With B-config.cmake:
include(CMakeFindDependencyMacro)
# All find_package we used in CMakeLists.txt should also be there
find_dependency(dependency1 REQUIRED)
find_dependency(dependency2 REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/B-targets.cmake")
If you have many target in B, you should install(TARGETS other-target EXPORT B-targets)
I strongly recommend looking at example of libraries that export their targets so you can base your work on it. For example, the glm library export its targets and always worked well. I also added target exportation in msdfgen project.
If you only want to use headers and not object files then just target_include_directories is all you need. Dependency tracking will be done by cmake and ninja/make automatically.
Alternatively, if you need to include both headers and objects from B, you'd really want to factor out these bits into small stand-alone static libraries within B, and then link with those libraries from A - this will add include file paths and everything else needed. cmake is quite good at it.

Accessing an external project with add-subdirectory results in CMake Error related to export set

I have a project A that depends on spdlog. Here is the structure:
|--- dir A
...|---src
......|---CMakeLists.txt
...|---include
...|---CMakeLists.txt
|---external/3rd_party/spdlog
I am trying to access spdlog in project A by adding a subdirectory. Here is how my A/CMakeLists.txt looks like:
cmake_minimum_required(VERSION 3.9.3 FATAL_ERROR)
project(GLOBAL CXX)
add_subdirectory(../external/3rd_party/spdlog ${CMAKE_BINARY_DIR}/spdlog EXCLUDE_FROM_ALL)
add_subdirectory(src)
Here is how my A/src/CMakeLists.txt looks like:
cmake_minimum_required(VERSION 3.9.3 FATAL_ERROR)
project(Alib CXX)
if(NOT TARGET spdlog)
# Stand-alone build
find_package(spdlog_header_only REQUIRED)
endif()
add_librray(A A.cpp)
target_link_libraries(A PUBLIC spdlog_header_only)
install(TARGETS A
EXPORT ATargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT ATargets
NAMESPACE A::
FILE ATargets.cmake
DESTINATION ${INSTALL_CONFIGDIR})
install(FILES AConfig.cmake DESTINATION ${INSTALL_CONFIGDIR})
When I try to build this, I get the following error:
CMake Error: install(EXPORT "ATargets" ...) includes target "A" which requires target "spdlog_header_only" that is not in the export set.
Please can you suggest me how to fix it?
For some reason I need to maintain the same directory structure I have shown above.
Here is a related question but does not have an answer : here
Meaining of the error
Since you use PUBLIC keyword when link your library (A) with spdlog_header_only, CMake expects that this linking is also needed for users of your library. So, when you create config file for your library (with install(EXPORT)), CMake adds linking with spdlog_header_only target into the config file too. Like
# [ATargets.cmake]
target_link_libraries(A::A PUBLIC spdlog_header_only)
Linking with a target implies existence of this target, but you do not install spdlog_header_only target. Because of that, created config file for your library won't work. This is what CMake tells you in the error message.
Simple fix
The simplest fix would be using PRIVATE linking with spdlog_header_only target, so that linking won't be part of the config file. Beware, in that case a user of your (installed) library won't get access to the header files for spdlog.
(But a user could obtain these headers by other means.)
Hard fix
But if you want a user of your library to have access to spdlog headers (or, worse, the public headers of your library use #include for headers from spdlog), then you cannot drop PUBLIC linking. In that case you need to install spdlog_header_only target too. E.g. by enabling SPDLOG_INSTALL option
set(SPDLOG_INSTALL ON)
before
add_subdirectory(../external/3rd_party/spdlog ${CMAKE_BINARY_DIR}/spdlog EXCLUDE_FROM_ALL)
(Note, that aside from enabling SPDLOG_INSTALL option, several other adjustments needs to be done for make the config file for your library to work.)

CMake Package Support - Includes and Libraries not found

I am currently developing a software package, for which I'd like to provide the cmake package support (so users can find it with find_package(...)). The Problem is, the package is found but FOO_INCLUDE_DIR and FOO_LIBRARIES is empty.
Within my package I have several modules, each with a CMakeLists file which installs the respective library and headers with:
install(TARGETS ${LIBRARY_NAME} EXPORT FooTargets
RUNTIME DESTINATION ${Foo_RUNTIME_INSTALL_DIR}
LIBRARY DESTINATION ${Foo_LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${Foo_ARCHIVE_INSTALL_DIR}
FRAMEWORK DESTINATION ${Foo_FRAMEWORK_INSTALL_DIR})
# Headers
install(
DIRECTORY include/${LIBRARY_NAME}
DESTINATION include/${PROJECT_NAME}
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
)
The headers for the library are included with target_include_directories like this:
target_include_directories(${LIBRARY_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # for headers when building
$<INSTALL_INTERFACE:${Foo_INC_INSTALL_DIR}> # for client in install mode
)
I checked the folders and all libraries and headers are correctly installed. In my toplevel CMakeLists I export my targets with:
install(
EXPORT FooTargets
DESTINATION ${Foo_CMAKE_CONFIG_INSTALL_DIR}
FILE FooConfig.cmake
)
The config is where I assume it to be (usr/local/lib/cmake/Foo). So everything seems to be correct. When I look into my FooConfig.cmake it says:
# Create imported target realm_densifier_base
add_library(FooLib1 SHARED IMPORTED)
set_target_properties(FooLib1 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "/usr/local/include/Foo"
INTERFACE_LINK_LIBRARIES "...several libraries..."
)
...which is absolutely correct and exactly what I expected. What part of the puzzle is missing? Is INTERFACE_INCLUDE_DIRECTORIES and INTERFACE_LINK_LIBRARIES not the correct flag to be set?
Thanks for the help and best regards,
Alex
Edit:
#Guillaume Racicot already cleared most things up, I only knew the "non target" way of adding headers to my project, that was with include_directories(Foo_INCLUDE_DIRS). However, in the target-world linking against my library Foo was enough. Another thing was that I messed up some directories in the target_include_directories(...) command, so directories were wrong and therefore could not be found in my other project. Thanks for the help!
Why would FOO_INCLUDE_DIR or FOO_LIBRARIES be set? This may be how old find modules worked, but not how config files work. Even newer find modules expose targets instead of directory variables.
When generating an XYZConfig.cmake file, informations about targets will be exported, not informations on the directory.
With such exportation:
install(
EXPORT FooTargets
NAMESPACE Foo::
DESTINATION ${Foo_CMAKE_CONFIG_INSTALL_DIR}
FILE FooConfig.cmake
)
You would expect users of the package to use it like so:
find_package(Foo REQUIRED)
# or PUBLIC ------v
target_link_libraries(bar PRIVATE Foo::FooLib1)
If your package has multiple targets in the export set, then you can link to both or only one
target_link_libraries(bar PRIVATE Foo::FooLib1 Foo::FooLib2)
target_link_libraries(baz PUBLIC Foo::FooLib2) # link to lib2 only
When you link to an exported target like Foo::FooLib1, its public interface will be transitively transmitted to its user. In the example above, bar will inherit properties from the linked target.
So the INTERFACE_INCLUDE_DIRECTORIES of Foo::FooLib1 and Foo::FooLib2 will be appended to bar's INCLUDE_DIRECTORIES. Same for LINK_LIBRARIES.
For baz, not only its INCLUDE_DIRECTORIES will contain Foo::FooLib2 entries, but also its own INTERFACE_INCLUDE_DIRECTORIES will transitively transmit the usage requirements of Foo::FooLib2

How does CMake know which prefixes and suffixes to add to shared libraries?

I am trying to use INSTALL in CMake to copy some external binaries to an install directory. My code goes like:
SET(SimTK_SHARED_LIBS
SimTKsimbody
SimTKmath
SimTKcommon
SimTKmolmodel
)
INSTALL(TARGETS ${SimTK_SHARED_LIBS}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
)
I get this error:
CMake Error at CMakeLists.txt:216 (INSTALL):
install TARGETS given target "SimTKsimbody" which does not exist in this
directory.
this is in spite of putting files called both libSimTKsimbody.so and (incorrectly) SimTKsimbody in the current directory as well as in the library directory.
Interestingly, this:
SET(SHARED_MMB_TARGET MMBlib)
ADD_LIBRARY(${SHARED_MMB_TARGET} SHARED
${MMB_LIBRARY_SOURCE_FILES}
${MMB_HEADER_FILES})
SET_TARGET_PROPERTIES(${SHARED_MMB_TARGET}
PROPERTIES
COMPILE_FLAGS "-DMMB_BUILDING_SHARED_LIBRARY"
PROJECT_LABEL "MMBlib (dynamic)")
TARGET_LINK_LIBRARIES(${SHARED_MMB_TARGET}
${SimTK_SHARED_LIBS_D}
${SimTK_SHARED_LIBS}
${OpenMM_SHARED_LIBS_D}
${OpenMM_SHARED_LIBS}
${SimTK_GENERAL_LIBS})
INSTALL(TARGETS ${SHARED_MMB_TARGET}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
)
.. works fine. It installs libMMBlib.so in ${CMAKE_INSTALL_PREFIX}/lib as it should. Does this mean that INSTALL will only work for this if I issue ADD_LIBRARY and/or SET_TARGET_PROPERTIES? The SimTK_SHARED_LIBS are compiled separately, I really do not want to compile them here.
I have thought about using INSTALL FILES, and just writing code to process the library names for each operating system. However I am convinced that CMake has the means to do this for me easily and elegantly.
Many thanks
Sam
Yes, you should use INSTALL(FILES) for install external libraries files.
CMake uses CMAKE_SHARED_LIBRARY_PREFIX and CMAKE_SHARED_LIBRARY_SUFFIX as default prefix and suffix for libraries created with add_library(... SHARED), so you may expect these components from external library:
INSTALL(FILES /path/to/library/${CMAKE_SHARED_LIBRARY_PREFIX}SimTKsimbody${CMAKE_SHARED_LIBRARY_SUFFIX}
...)
Also you may use FIND_LIBRARY for automatic(and nice) check of your expectations about library suffix and prefix:
FIND_LIBRARY(SIMTK_SIMBODY_LIB
${CMAKE_SHARED_LIBRARY_PREFIX}SimTKsimbody${CMAKE_SHARED_LIBRARY_SUFFIX}
PATH /path/to/library)
INSTALL(FILES ${SIMTK_SIMBODY_LIB} ...)