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

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.

Related

What's a simple straightforward find_package set of files for CMake

I'm new(ish) to CMake (meaning off and on over a few years I've been forced to use it even though it's something that's made me question my career path).
I have a folder with an include folder, and a lib folder containing .lib files and their corresponding .dlls for a dependency I'll call "mydep". I need to provide the infrastructure files, mydep-config.cmake, mydep-target.cmake, etc, which will add the includes folders and .lib files to the command line for compiling and linking and then move the .dlls to a specific location.
Can anyone point me to a simple example anywhere on the net that illustrates the way to do this? The CMake documentation is utterly useless.
Thanks!
There are basically 2 files you need to put in the correct location on the file system. Let's assume the library is to import is called ExternLib with version 1.2.3 and compiled for 64 bit. With the and you've got it stored in your repo as
external_dependencies/lib/ExternLib.lib
external_dependencies/bin/ExternLib.dll
external_dependencies/include/ExternLib/ExternHeader.hpp
external_dependencies/include/ExternLib/...
...
and you want the include paths used in your project to be
#include "ExternLib/ExternHeader.hpp"
First of all we'll use the file names externlib-config.cmake and externlib-version-config.cmake to allow the user to use arbitrary case when using find_package.
externlib-version-config.cmake
This file is used to check, if the version of the config script is compatible with the cmake configuration trying to find the package. Adding this file allows you to place multiple versions of the library to be found for different target architectures (e.g. Windows x64, Win32, linux x86_64, ...). find_package won't read the externlib-config.cmake in the same directory, if the version is marked as non-compatible via the version file.
set(PACKAGE_VERSION "1.2.3")
# basically ignore the version passed
set(PACKAGE_VERSION_COMPATIBLE TRUE) # consider every version compatible
#usually you'd add logic for ignoring any unspecified parts of the version here instead of treating unspecified version parts as 0
# we'll keep it simple though
if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE) # make version as exact match
endif()
if(NOT WIN32)
set(PACKAGE_VERSION_COMPATIBLE FALSE) # disallow older version
else()
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") # note: we'll ignore the possibility of find_package before the first project() command
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") # check for 64 bit
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
externlib-config.cmake
Here we'll put the creation of the actual library in addition to providing any functions/macros/variables the user should have access to. We'll use ExternLib::ExternLib as target name, but you can simply replace this by any name of your choosing unique in your project.
if(NOT TARGET ExternLib::ExternLib) # prevent recreation on multiple uses of find_package
add_library(ExternLib::ExternLib SHARED IMPORTED)
get_filename_component(EXTERNLIB_BASE_DIR ${CMAKE_CURRENT_LISTS_DIR}/../../.. ABSOLUTE)
set(EXTERNLIB_BINARY_DIR ${EXTERNLIB_BASE_DIR} CACHE INTERNAL "Directory containing the dlls for ExternLib")
# add info about locations of the dll/lib files
set_target_properties(ExternLib::ExternLib PROPERTIES
IMPORTED_LOCATION "$CACHE{EXTERNLIB_BINARY_DIR}/ExternLib.dll"
IMPORTED_IMPLIB "${EXTERNLIB_BASE_DIR}/lib/ExternLib.lib"
)
# add include directory information
target_include_directories(ExternLib::ExternLib INTERFACE "${EXTERNLIB_BASE_DIR}/include")
# add dependencies, if required; you may need to use find_package to locate those
# target_link_libraries(ExternLib::ExternLib INTERFACE ...)
endif()
# any helper function/macro definitions should go here, since they may need to get reintroduced
Note: I did ignore the possibility of components of a package being specified here. Any components specifying components for find_package are simply ignored for the script above.
Placement of the files
You'll need to place the files in one of some possible paths below one of the directories mentioned in CMAKE_PREFIX_PATH or in one of the default dirs, see find_package Config Mode Search Procedure.
Note: we'll use a path that isn't documented for windows, since this path would also work for linux. (Boost is doing this too.)
lib/cmake/ExternLib-x64-1.2.3
(You could choose a different suffix to lib/cmake/ExternLib or just use lib/cmake/ExternLib. The search procedure picks up on any directory names which starts with the package name ignoring case, if it expects the lib name.)
Place both the files in this directory. externlib-config.cmake assumes lib is external_dependencies/lib here. Otherwise you may need to adjust EXTERNLIB_BASE_DIR accordingly.
Usage
We'll assume the CMakeLists.txt file is placed in the same directory as external_dependencies
project(...)
...
add_executable(my_exe ...)
...
# this allows the user to pass directories to be searched first via -D option during configuration
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external_dependencies)
find_package(ExternLib REQUIRED)
target_link_libraries(my_exe PRIVATE ExternLib::ExternLib)
# allow vs debugger to find the dll without copying the dlls around
set_target_properties(my_exe PROPERTIES
VS_DEBUGGER_ENVIRONMENT "PATH=${EXTERNLIB_BINARY_DIR};$ENV{PATH}"
)

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

cmake: how to reference and build separate cmake project dependency?

I have a cross-compiler cmake project that depends on libraries from a separate project that happens to also use cmake:
/myProject/CMakeLists.txt (uses cross-compiler)
/anotherProject/CMakeLists.txt (platform-agnostic)
anotherProject can be built completely separately on its own. It has no knowledge of myProject at all.
Now, anotherProject has numerous modules that I need, like:
anotherProject/A/CMakeLists.txt (produces static lib A.a)
anotherProject/B/CMakeLists.txt (produces static lib B.a)
etc
When I build myProject, I want to build and link against anotherProject/A and anotherProject/B, to produce shared lib myproject.so. I'd like to leverage the existing cmake-ness of anotherProject if possible, as opposed to manually globbing its various source sets from myProject.
What's the correct way to achieve this with cmake? I feel like I'm missing something obvious.
It would be straightforward if, say, myProject were just a subdirectory under anotherProject, or if there were a top-level CMakeLists.txt that could reference both myProject and anotherProject; but neither is what I'm after. I know I could build anotherProject and export its libraries to a well-known location, and then reference the export directory from myProject - but I would like to avoid that setup as well.
A solution is to use CMake packages.
Basically, in anotherProject, you craft a CMake configuration file where you set variables to be used by myProject (eg. include directory, list of libraries, compilation flags...), or even targets.
Then, in myProject, you use the find_package() mechanism so that CMake finds this configuration file and imports the variables/targets in your current project.
There is a tutorial on the CMake wiki.
The only alternative setup that I can think of based on your requirements is to allow your main (dependent) project to discover the other (dependee) project using find_package.
In your main project CMakeLists.txt you should add something like this:
find_package(anotherProject CONFIG)
if(anotherProject_FOUND)
message(STATUS "Found project dependency: anotherProject")
else
# change WARNING to FATAL_ERROR if the dependency is NOT optional
message(WARNING "package anotherProject was not found")
endif()
On the differences between CONFIG and MODULE modes, check the documentation and this link.
Then assuming that your main project creates an executable, you could hook up the discovered dependency like this:
add_executable(myProject ${SOURCES})
[...]
if(anotherProject_FOUND)
target_link_libraries(myProject PUBLIC anotherProject)
endif()
This should take care of the required include files and definitions as well.
Now in the dependee project CMakeLists.txt you should do something like this:
set(PRJ_NAME "anotherProject")
string(TOLOWER ${PRJ_NAME} PRJ_NAME_LOWER)
set(ANOTHERPROJECT_EXPORT_NAME "${PRJ_NAME}")
install(TARGETS ${PRJ_NAME} EXPORT ${ANOTHERPROJECT_EXPORT_NAME}
RUNTIME DESTINATION .)
install(EXPORT ${ANOTHERPROJECT_EXPORT_NAME} DESTINATION "share/cmake")
This associates an export with a target and then installs the export.
Now, if you check that export file, it expects certain things to be found and included, that could be specific for your project. To make this as supple as possible, you can use the configure feature to generate them from a template and then install from the build directory.
So, in the project under a subdir named share/cmake you could have a file named config.cmake.in with contents:
include(${CMAKE_CURRENT_LIST_DIR}/#PRJ_NAME#.cmake)
In the main project's CMakeLists.txt you need to add the following for generating the file from that template:
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/share/cmake/config.cmake
${CMAKE_CURRENT_BINARY_DIR}/share/cmake/${PRJ_NAME_LOWER}-config.cmake)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/share/
DESTINATION share)
Notice that I used PRJ_NAME, because you could potentially reuse that to name the actual executable at the add_executable command. It mentally helps if the exported target has the same name with produced one.
This is a more versatile version to accommodate multiple subprojects of this tutorial.

CMake not building a library when added as a subdirectory

I added the xgboost library as a git submodule of my project and I'm trying to add it to cmake as a subdirectory. Unfortunately it's not working. A simple hello world project with the following CMakeLists.txt replicates the error that I'm getting.
cmake_minimum_required(VERSION 3.2)
project(foo)
add_subdirectory(xgboost)
add_executable(${PROJECT_NAME} foo.cpp)
target_link_libraries(${PROJECT_NAME} xgboost)
After building the library there is nothing in the xgboost/lib directory so I get the following error.
clang: error: no such file or directory:
'/Users/.../myproject/xgboost/lib/libxgboost.dylib'
I think that the problem is generated in their CMakeLists file since they have two different targets. Maybe cmake is choosing the wrong target but I'm not familiar enough with cmake to figure it out. The following code is from xgboost's CMakeLists.
# Executable
add_executable(runxgboost $<TARGET_OBJECTS:objxgboost> src/cli_main.cc)
set_target_properties(runxgboost PROPERTIES
OUTPUT_NAME xgboost
)
set_output_directory(runxgboost ${PROJECT_SOURCE_DIR})
target_link_libraries(runxgboost ${LINK_LIBRARIES})
# Shared library
add_library(xgboost SHARED $<TARGET_OBJECTS:objxgboost>)
target_link_libraries(xgboost ${LINK_LIBRARIES})
set_output_directory(xgboost ${PROJECT_SOURCE_DIR}/lib)
#Ensure these two targets do not build simultaneously, as they produce outputs with conflicting names
add_dependencies(xgboost runxgboost)
My questions in order of importance are:
Is there any way to fix it without modifying xgboost's CMakeLists.txt file?
Is it reasonable to try to add xgboost to my project as a git submodule?
Is there any reason cmake is not instructing to build the library?
Note: There were several edits to this question since I tried to narrow down the problem and to provide more information.
(I would love to ask for few things beforehand in the comment section, but I have too low reputation to do so, so I will just give it a shot ;))
I have few suspects, and one of them is ${CMAKE_SOURCE_DIR} of the submodule's root CMakeLists.txt. Although the paths are set properly when you run that CMakeLists.txt alone, cmake gets confused the moment you add it as your subdirectory. Have you looked into another directories for your output binaries?
First I would suggest testing this hypothesis, and then I would suggest writing similar, but separate CMakeLists.txt file for xgboost library, and then substitute it in the project temporarily. Unfortunately the CMakeLists.txt filename is hardcoded and there is no possibility to have two files of that kind in one directory; so it seems that the answer to 1) is, that you rather have to change the file.
For the 2): as long as it does not require huge additional logic in your CMakeLists.txt, it makes sense. Other viable option is to create an install target, which you can use to install your xgboost library locally (using CMAKE_INSTALL_PREFIX(doc) variable), and then add the installation path to your CMAKE_LIBRARY_PATH(doc).

cmake - find_library - custom library location

I'm currently trying to get CMake running for my project (on windows). I want to use a custom location where all libraries are installed. To inform CMake about that path I tried to do that:
set(CMAKE_PREFIX_PATH D:/develop/cmake/libs)
But when I try to find the library with
find_library(CURL_LIBRARY NAMES curl curllib libcurl_imp curllib_static)
CMake can't find it.
When I set my prefix path to
set(CMAKE_PREFIX_PATH D:/develop/cmake/libs/curl)
... the library is located.
So my question is:
How can I configure CMake properly to work with a directory structore at a custom location which looks like that:
D:/develop/cmake/libs/
-> libA
-> include
-> lib
-> libB
-> include
-> lib
-> ...
-> include
-> lib
In "include" lie the public headers and in "lib" are the compiled libraries.
edit:
The current workaround for me is, to do this before i search for libraries:
set(CUSTOM_LIBRARY_PATH D:/develop/cmake/libs)
file(GLOB sub-dir ${CUSTOM_LIBRARY_PATH}/*)
foreach(dir ${sub-dir})
if(IS_DIRECTORY ${dir})
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH};${dir})
endif()
endforeach()
But that way the default module for boost wont find it until it because the directory structore of boost is a bit different.
boost -> include -> boost-1_50 -> *.hpp
When I move the content if "boost-1_50" to "include" the library can be found but that way it's not possible to handle multiple versions right?
The simplest solution may be to add HINTS to each find_* request.
For example:
find_library(CURL_LIBRARY
NAMES curl curllib libcurl_imp curllib_static
HINTS "${CMAKE_PREFIX_PATH}/curl/lib"
)
For Boost I would strongly recommend using the FindBoost standard module and setting the BOOST_DIR variable to point to your Boost libraries.
I saw that two people put that question to their favorites so I will try to answer the solution which works for me:
Instead of using find modules I'm writing configuration files for all libraries which are installed. Those files are extremly simple and can also be used to set non-standard variables. CMake will (at least on windows) search for those configuration files in
CMAKE_PREFIX_PATH/<<package_name>>-<<version>>/<<package_name>>-config.cmake
(which can be set through an environment variable).
So for example the boost configuration is in the path
CMAKE_PREFIX_PATH/boost-1_50/boost-config.cmake
In that configuration you can set variables. My config file for boost looks like that:
set(boost_INCLUDE_DIRS ${boost_DIR}/include)
set(boost_LIBRARY_DIR ${boost_DIR}/lib)
foreach(component ${boost_FIND_COMPONENTS})
set(boost_LIBRARIES ${boost_LIBRARIES} debug ${boost_LIBRARY_DIR}/libboost_${component}-vc110-mt-gd-1_50.lib)
set(boost_LIBRARIES ${boost_LIBRARIES} optimized ${boost_LIBRARY_DIR}/libboost_${component}-vc110-mt-1_50.lib)
endforeach()
add_definitions( -D_WIN32_WINNT=0x0501 )
Pretty straight forward + it's possible to shrink the size of the config files even more when you write some helper functions. The only issue I have with this setup is that I havn't found a way to give config files a priority over find modules - so you need to remove the find modules.
Hope this this is helpful for other people.
Use CMAKE_PREFIX_PATH by adding multiple paths (separated by semicolons and no white spaces). You can set it as an environmental variable to avoid having absolute paths in your cmake configuration files
Notice that cmake will look for config file in any of the following folders
where is any of the path in CMAKE_PREFIX_PATH and name is the name of the library you are looking for
<prefix>/ (W)
<prefix>/(cmake|CMake)/ (W)
<prefix>/<name>*/ (W)
<prefix>/<name>*/(cmake|CMake)/ (W)
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
In your case you need to add to CMAKE_PREFIX_PATH the following two paths:
D:/develop/cmake/libs/libA;D:/develop/cmake/libB
There is no way to automatically set CMAKE_PREFIX_PATH in a way you want. I see following ways to solve this problem:
Put all libraries files in the same dir. That is, include/ would contain headers for all libs, lib/ - binaries, etc. FYI, this is common layout for most UNIX-like systems.
Set global environment variable CMAKE_PREFIX_PATH to D:/develop/cmake/libs/libA;D:/develop/cmake/libs/libB;.... When you run CMake, it would aautomatically pick up this env var and populate it's own CMAKE_PREFIX_PATH.
Write a wrapper .bat script, which would call cmake command with -D CMAKE_PREFIX_PATH=... argument.
You have one extra level of nesting.
CMAKE will search under $CMAKE_PREFIX_PATH/include for headers and $CMAKE_PREFIX_PATH/libs for libraries.
From CMAKE documentation:
For each path in the CMAKE_PREFIX_PATH list, CMake will check
"PATH/include" and "PATH" when FIND_PATH() is called, "PATH/bin" and
"PATH" when FIND_PROGRAM() is called, and "PATH/lib and "PATH" when
FIND_LIBRARY() is called.
I've encountered a similar scenario. I solved it by adding in this following code just before find_library():
set(CMAKE_PREFIX_PATH /the/custom/path/to/your/lib/)
then it can find the library location.