cmake: create a new library target which consists of a prebuilt library - c++

I'm trying to create a custom target in cmake which:
links a vendor supplied archive
adds target_compile_options(... PUBLIC ...)
adds target_include_directories(... PUBLIC ...)
In this way consumers can link against my new target and have requisite include directories and compile options set.
In boost-build you can use the <file> feature to "wrap" or "alias" a prebuilt library, and add additional "usage requirements" (compiler flags etc) which will be added to all targets using the library.
lib foo
:
: <file>vendor/library.a
:
: <include>vendor
;
This aliases vendor/library.a as a new library foo.
When another target uses foo it will also have its include paths updated.
The code using foo will then be able to #include "foo.h", and the file will be found because vendor has been added to the include paths.
I'm looking for a way to do the same thing in CMake.
The way I would currently do it would be something along these lines:
find_library(LIB_FOO library.a PATHS ${CMAKE_SOURCE_DIR}/path/to/vendor NO_DEFAULT_PATH)
target_link_library (my_target ${LIB_FOO})
target_include_directories(my_target PRIVATE "${CMAKE_SOURCE_DIR}/path/to/vendor")
However, if there are several targets which need to use foo then these 3 calls would have be repeated for each of them, which becomes quite onerous.
It would be far easier for consumers of foo to have some other target which has
target_link_libraries (foo ${CMAKE_CURRENT_LIST_DIR}/vendor/library.a)
target_compile_options (foo PUBLIC ...)
target_include_directories(foo PUBLIC ...)
Question:
Can I create a new library target which consists of a prebuilt library which will allow me to do what I've described?
Edit in response to comment:
I have tried to create an IMPORTED library (as described here):
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/vendor/library.a)
However, when I then try to set include directories I get an error:
target_include_directories(foo SYSTEM PUBLIC "${CMAKE_CURRENT_LIST_DIR}/vendor")
Error:
CMake Error at vendor/CMakeLists.txt:39 (target_include_directories):
Cannot specify include directories for imported target "foo".

You need to import a pre-built library. here is the tutorial how to do it.
To make the resulted target "complete" you need to add certain properties to it. Unfortunately this can't be done using usual techniques, because of this CMake issue. So you have to set these props manually, like described in this SO QA

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

In cmake, can we avoid using find_package to import other packages?

Okay, the title is obviously weird. I hope anyone who can define my curiosity cleanly, would edit the title into proper one.
When using CMakeLists.txt, we usually import third-party packages via command find_package() macro, which looks for proper find_XXX.cmake script, and then results outputting variables such as XXX_LIBRARIES, XXX_INCLUDE_DIRECTORIES, etc.
However, recently I found some instruction guide for add_library() macro, that can include actual library binaries as its source(?), like this way:
add_library(my_target STATIC IMPORTED foo.lib)
And as I guess, as long as the name my_target is actually names a target, then we can add another compositions into that target, like any other cmake targets, like below.
target_inlcude_directories(my_target PUBLIC ${PKG_PREFIX}/include)
target_link_directories(my_target PUBLIC ${PKG_PREFIX}/lib)
target_compile_features(my_target PUBLIC std_cxx_17)
If this is possible, then wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
add_executable(foo ${MY_SOURCES})
find_package(xxx)
# BEFORE
target_link_directories(foo PRIVATE ${XXX_LINK_DIRECTORIES}) # note: I don't remember what was it exactly.
target_link_libraries(foo PRIVATE ${XXX_LIBRARIES})
target_include_directories(foo PRIVATE ${XXX_INCLUDE_DIRECTORIES})
# AFTER
target_link_libraries(foo PRIVATE xxx::my_target)
Am I thinking something invalid?
can we avoid using find_package to import other packages?
You can, but it's the tool to be used just for that. find_package() is basically an include() with some special options.
wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
It would be and it is used.
cmake is still ongoing relative big changes in a very short time. I think interface libraries weren't so popular or weren't available some time ago. The only (except some checks, etc) thing find_package does is it includes a file named FindXXX.cmake (or other file named differently). Only. It's completely dependent on what's inside the FindXXX.cmake file what happens.
Newer cmake FindXXX.cmake does exactly what you propose: they create an INTERFACE IMPORTED library, for example on my pc:
#/usr/share/cmake-3.20/Modules
$ grep add_library -wr --include='Find*' .
.... a lot of results ....
./FindBoost.cmake: add_library(Boost::diagnostic_definitions INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::disable_autolinking INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::dynamic_linking INTERFACE IMPORTED)
./FindThreads.cmake: add_library(Threads::Threads INTERFACE IMPORTED)
./FindHDF5.cmake: add_library(HDF5::HDF5 INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindGTest.cmake: add_library(GTest::GTest INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::Main INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest ${GTEST_LIBRARY_TYPE} IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest_main ${GTEST_MAIN_LIBRARY_TYPE} IMPORTED)
./FindBZip2.cmake: add_library(BZip2::BZip2 UNKNOWN IMPORTED)
Older or "more standard" (ie. older :) indeed create multiple variables - XXX_LINK_DIRECTORIES XXX_LIBRARIES etc. Note that does variable names are not the same everywhere (XXX_LIB? XXX_INC_DIRS? etc.) and change depending on which FindXXX.cmake you call because some developers decided on different names. A lot of documentation was written in the time the find_package used variables, so it's still visible in documentation, but nowadays interface libraries clearly dominate and what you propose is already used.
Note that the imported library is not set up properly in this case. Configuration scripts (<packageName>Config.cmake) which are used as fallback for find_package or if you use the CONFIG version usually do exactly this: create one or more imported targets for you to use in your project. I strongly recommend using those instead of the *_LIBS/*_DIRS variables, if available.
Here's the correct version of importing the library:
add_library(my_target STATIC IMPORTED)
# INTERFACE "visibility" needed here;
# also quote the path concatenation to avoid issues with spaces in PKG_PREFIX
target_include_directories(my_target INTERFACE "${PKG_PREFIX}/include")
target_link_directories(my_target INTERFACE "${PKG_PREFIX}/lib")
# do you really need the linking library to use the C++ 17 standard?
target_compile_features(my_target INTERFACE std_cxx_17)
# you need specify the absolute path to the lib as IMPORTED_LOCATION target property
# you may need to adjust the location according to the exact location of the lib file
#
# variables CMAKE_CURRENT_SOURCE_DIR (if used from CMakeLists.txt) or
# CMAKE_CURRENT_LIST_DIR (if used from a script) may be helpful
#
# requires seperate treatment for different OS; this is not included here
set_target_properties(my_target PROPERTIES IMPORTED_LOCATION "${PKG_PREFIX}/lib/my_target.a")
Note that in this case the library you need to link is my_target, not xxx::my_target.

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.

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 to link external C++ library to my project on Mac OSX [duplicate]

I have 2 folders "inc" and "lib" in my project which have headers and static libs respectively. How do I tell cmake to use those 2 directories for include and linking respectively?
The simplest way of doing this would be to add
include_directories(${CMAKE_SOURCE_DIR}/inc)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(foo ${FOO_SRCS})
target_link_libraries(foo bar) # libbar.so is found in ${CMAKE_SOURCE_DIR}/lib
The modern CMake version that doesn't add the -I and -L flags to every compiler invocation would be to use imported libraries:
add_library(bar SHARED IMPORTED) # or STATIC instead of SHARED
set_target_properties(bar PROPERTIES
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/lib/libbar.so"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libbar"
)
set(FOO_SRCS "foo.cpp")
add_executable(foo ${FOO_SRCS})
target_link_libraries(foo bar) # also adds the required include path
If setting the INTERFACE_INCLUDE_DIRECTORIES doesn't add the path, older versions of CMake also allow you to use target_include_directories(bar PUBLIC /path/to/include). However, this no longer works with CMake 3.6 or newer.
You had better use find_library command instead of link_directories. Concretely speaking there are two ways:
designate the path within the command
find_library(NAMES gtest PATHS path1 path2 ... pathN)
set the variable CMAKE_LIBRARY_PATH
set(CMAKE_LIBRARY_PATH path1 path2)
find_library(NAMES gtest)
the reason is as flowings:
Note This command is rarely necessary and should be avoided where there are other choices. Prefer to pass full absolute paths to
libraries where possible, since this ensures the correct library will
always be linked. The find_library() command provides the full path,
which can generally be used directly in calls to
target_link_libraries(). Situations where a library search path may be
needed include: Project generators like Xcode where the user can
switch target architecture at build time, but a full path to a library
cannot be used because it only provides one architecture (i.e. it is
not a universal binary).
Libraries may themselves have other private library dependencies that
expect to be found via RPATH mechanisms, but some linkers are not able
to fully decode those paths (e.g. due to the presence of things like
$ORIGIN).
If a library search path must be provided, prefer to localize the
effect where possible by using the target_link_directories() command
rather than link_directories(). The target-specific command can also
control how the search directories propagate to other dependent
targets.
might fail working with link_directories, then add each static library like following:
target_link_libraries(foo /path_to_static_library/libbar.a)