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

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...

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 use pb.cc to make libraries in subdirectories, i got "Cannot find source file"

I tries to use FOREACH to generated several pb files. And make two list names PROTO_SRCS & PROTO_HDRS like below.
I can use it in the main CMakeLists. Like add_executable(a SHARED ${PROTO_SRCS} main.cpp).
But I can not use this param in subdirectories to make a library. when I type "cmake .." in main CMakelists build dir. It shown that "Cannot find source file: a.pb.cc".
main CMakeLists.txt
add_library(xxx SHARED ${PROTO_SRCS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/back back)
in src/back CMakeLists.txt
add_executable(yyy ${PROTO_SRCS})
and I can use message to show ${PROTO_SRCS} in subdir so it pass into successfully.
Please help me to point out the problem. Thx a lot
The issue is that in CMake versions older than 3.20 the GENERATED property of source files is only visible in the directory where it is set. Thus, when you add the protobuf-generated source files to a target defined in a different directory, CMake will no longer know that these are files generated during the build. Consequently, CMake will try to locate these files at configuration time, when they obviously do not exist yet.
Unfortunately, at the time of writing there is only a release candidate for CMake 3.20 and no official release yet. So depending on whether you need to coordinate with other coworkers or whether you're working on this project on your own it might not be feasible to use the release candidate.
If you can't use it, the alternative is to create an object library via add_library(protobuf_objs OBJECT ${PROTO_SRCS}) in the directory where you generate the files and to use target_sources(xxx PRIVATE $<TARGET_OBJECTS:protobuf_objs>) and target_sources(yyy PRIVATE $<TARGET_OBJECTS:protobuf_objs>) instead of adding the ${PROTO_SRCS} as source files to these targets directly.

If I find_package in CMakeLists.txt, must I find_dependency in my installed config.cmake?

I'm using CMake to build and to install a certain library, foo.
My library depends on some other library, bar, which has a config CMake script, so I have:
find_package(bar REQUIRED)
target_link_libraries(foo PUBLIC bar::bar)
That's as far as building goes. For installation, I have appropriate install() commands, with exports, and version configuration and all that stuff. This generates the package's -config.cmake file (as well as a version config file), so I don't need to keep one in the repository, nor generate one line-by-line within my CMakeLists.txt
Now, CMake has a module named find_dependency(), whose documentation suggests is to be used in package configuration files. But - I don't explicitly add it there. Should I? And more generally: Under which circumstances should I manually ensure a package configuration file has a find_dependency() for various find_package()'s?
First, CMake does not support "Transitive" behavior for find_package() (check this question).
The documentation recommends that "All REQUIRED dependencies of a package should be found in the Config.cmake file":
# <package>Config.cmake file
include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake") # They depend on Stats
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")
So, answering your questions:
"I don't explicitly add it there. Should I?" You actually have to, at least for REQUIRED packages.
"Under which circumstances should I manually ensure a package configuration file has a find_dependency() for various find_package()'s?" For required packages, you must. For optional package, you might want to add it in the configuration file so that optional features will be available.
I am working on a project which depends on an external package (Catch2). In my top level CMakelists.txt I have:
# Top level CMakelists.txt
set(Catch2_DIR "${PATH_TO_CATCH2}/lib/cmake/Catch2/")
find_package(Catch2 ${CATCH2_VERSION} REQUIRED)
Then I added the following to my package configuration file:
# <package>Config.cmake file
include(CMakeFindDependencyMacro)
set(Catch2_DIR "#PATH_TO_CATCH2#/lib/cmake/Catch2/") #be careful, path hard coded
find_dependency(Catch2 REQUIRED)
Just be careful because the find_dependency is a macro and it will change the value of PACKAGE_PREFIX_DIR variable in your package configuration file.

How to make include directories added with AUTOUIC available to downstream targets?

I have a problem with CMake's AUTOUIC option in my qt project.
I have a target that has *.ui qt-form files and changed it to use the AUTOUIC option to automatically generate the corresponding ui_*.h files and add their location to the target's include directory.
The problem is, that I inlcude the generated "ui_*.h" file in a header of the target, which is then included in another test-target. The test-target however does not have the directory with the generated files set to its include directories and therefore will not find the ui_*.h file.
So is there any way to get the directory of the generated files so I can add it to the INTERFACE_INCLUDE_DIRECTORIES of my first target. When I do this using hardcoded names It solves my compile errors, but I would rather do this by getting that directory from some target property or so. I failed with that because the AUTOUIC include dirs seem to be added only after processing all the CMakeLists files.
Btw., I am currently using CMake 3.8.2, but updating to 3.9 is an option if the problem has been solved there.
From CMake version 3.9 onwards you can use the following snippet to add the autogenerated headers to a target's build interface include directories:
get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(_isMultiConfig)
set(AUTOGEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_autogen/include_$<CONFIG>)
else()
set(AUTOGEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_autogen/include)
endif()
target_include_directories(${TARGET_NAME} INTERFACE
$<BUILD_INTERFACE:${AUTOGEN_INCLUDE_DIR}>
)
See:
Use GENERATOR_IS_MULTI_CONFIG to detect multi-config generators
AUTOUIC
GENERATOR_IS_MULTI_CONFIG
AUTOGEN_BUILD_DIR
I have the same issue - it looks like a bug in CMAKE (I'm using version 3.10 rc5)
and have opened a bug report to CMAKE here: https://gitlab.kitware.com/cmake/cmake/issues/17456
In the meantime you can copy the autgenerated header files to your project source directory by using the following commands after autouic has been run.
SET(AUTOGEN_BUILD_DIR "${CMAKE_BUILD_DIR}/<project>_autogen/include_${CONFIG}")
file(COPY "${AUTOGEN_BUILD_DIR}/ui_xxxx.h" DESTINATION "${CMAKE_SOURCE_DIR}/headers")
where ${CONFIG} is the type of build (Debug/Release)

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} ...)