How to install a target that link privately against an Interface library - c++

Hello developper friends, I'm using CMake for a while now, but I could't figure how to install a target :
that is part of an export set
that link privately against interface library
The export set is created with the command
install(EXPORT MyExportSet DESTINATION MyExportDir
NAMESPACE Project FILE ProjectTargets.cmake)
the link to the interface library is done like this
target_link_libraries(exportedTarget
PRIVATE interfaceTargetLibrary
)
and finally i'm exporting the target like this :
install(TARGETS exportedTarget EXPORT MyExportSet
ARCHIVE DESTINATION lib/static
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
This look rights to me, and following the logic of the PRIVATE flag uppon linking, I shouldn't have to export the target interfaceTargetLibrary
But CMake throw me an error (This one)
CMake Error: install(EXPORT "ProjectTargets" ...) includes target"exportedTargets" which requires target "interfaceTargetLibrary" that is not in the export set.
So here my question, is that a bug ? or something I didn't understand ? And obviously did you achieve to make it work on way or another.
I'm using CMake version 3.7.
EDIT : Finded out, if someone interested. It's because, the library that is linked privately wont be packaged in the one I export. So, the comsummer of the library will need, to link against interfaceTargetLibary
too and so I need to export it as well.

Since it's linked privately, you can use the following trick: CMake won't try to install an IMPORTED library.
Also see "What is an INTERFACE IMPORTED library in CMake and what are its uses?".
I tried to find official documentation on this behaviour but I could not. I did find a related discussion ticket on kitware's repo though.

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

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 export custom target with CMake?

I'm trying to export all of the dependent targets of an engine I've been developing with export command. Everything works fine with dependencies, but when I call the command with main "Nabla" target, I get a following error:
CMake Error in CMakeLists.txt:
export called with target "Nabla" which requires target "openssl_build"
that is not in any export set.
The problem is that the openssl_build is a custom target and I have no clue how to avoid this error, because when I try to export the target I get another error telling me that
-- Using static CRT ON
CMake Error at 3rdparty/CMakeLists.txt:556 (export):
export given custom target "openssl_build" which may not be exported.
the following commit contains my changes to the engine in reference to export command
The custom target generating the error is here
I wonder if I could set a cmake property for the openssl_build to make it work, but I have been looking for useful properties in cmake docs and could not find anything
Thank you in advance!
This usually happens when you use add_subdirectory to consume library.
When you add add_subdirectory, CMake will consider, for example, the whole openssl project become part of your project. They are not separable if they are the same project.
If you build openssl as part of your project, it's very doubting that your project will run properly without also having openssl installed, let alone users consuming your package!
You could simply add the openssl libraries you depend on to the export set:
install(TARGETS openssl_build EXPORT NablaTargets)
But that's not the proper way.
The proper way would be to use a package manager, such as vcpkg to install the dependencies for you.
First, replace the add_subdirectory by a find_package and a link:
find_package(OpenSSL REQUIRED)
target_link_libraries(Nabla PUBLIC OpenSSL::SSL OpenSSL::Crypto)
Create vcpkg.json with this content:
{
"name": "nabla",
"version-string": "0.1",
"dependencies": [
"openssl"
]
}
And when you use cmake, add the argument -DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake (or where you installed it) and let the package manager do the work for you.
Remember that when you use a new toolchain file, you must re-create a new build directory since it cannot be added after creating it.

Force CMake target_link_libraries to fail when adding nonexistent target

CMake has an irritating default (I presume, I see nothing magical in my CMake config, but I could be wrong since I know very little about CMake) behavior that he silently ignores when you add a target to your project even if that target does not exist, for example:
project(StackOverflow)
// another CMakeLists.txt
project (Stuff)
target_link_libraries(Stuff
PUBLIC StackOverlow )
Is there a way to force CMake to check that all projects you link in target_link_libraries must exist?
It is possible for CMake to fail if you link ALIAS targets. For example
In first CMakeLists.txt
add_library(StackOverflow STATIC lib.cpp)
add_library(StackOverflow::StackOverflow ALIAS StackOverflow)
In second CMakeLists.txt
target_link_libraries(Stuff PUBLIC StackOverflow::StackOverflow)
CMake will fail with an error if StackOverflow::StackOverflow is not defined.
https://cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html#alias-targets
In CMake, you do not link projects to other projects. Instead, you link targets to other targets.
CMake targets are only created via a few commands (such as add_library, add_executable, and add_custom_target). The project command does not create a CMake target, it merely declares a project.
Furthermore, the target_link_libraries() command accepts the following arguments after the scoping keyword:
A library target name
A full path to a library file
A plain library name
A link flag
A generator expression
A debug, optimized, or general keyword
It does not accept project names, although if you put a project name, it will instead look for a CMake target or library file on your system with that name.
To get to the root of what I believe you're asking: If you provide link-item name to target_link_libraries() that does not match an existing target, the command will simply search for a library file of that name instead.
To check if a target exists before trying to link it, you can do:
if (TARGET StackOverflow)
target_link_libraries(Stuff PUBLIC StackOverflow)
endif()
I suggest reading through the linked target_link_libraries() documentation if you want more details about what this command does.

How do I export a target, then use it in another project via ExternalProject?

I have a CMake project named proj1, which I want to use as an external project in another project, proj2. Now, the (relevant) command in proj1's CMakeLists.txt is:
install(
TARGETS proj1
ARCHIVE
DESTINATION lib
EXPORT proj1_library
INCLUDES DESTINATION include
CONFIGURATIONS Release RelWithDebugInfo
)
and I want to use this static library in proj2, without explicitly "guessing" where it's installed to be proj1. I want to be able to get obtain this target from proj1 (which I obtain using ExternalProject), then use it - directly or indirectly - in add_target_libraries() commands.
How should I do that? And - do I only need to make changes to proj2 or also to proj1's CMakeLists.txt?
Exporting the targets is the right approach.
To support this, proj1 would have to generate a proj1Config.cmake in both its build tree and also in the install tree (so that a development package for proj1 can be used as a SDK [1])
I suggest you read the following section of the CMake documentation, it covers the different concepts and provides an example. See https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#creating-packages
[1] SDK: https://en.wikipedia.org/wiki/Software_development_kit