Now I have a lib I made my self that I want to use in another CMake c++ project. It exists in my computer like this.
${MY_LIB_PATH}\include
${MY_LIB_PATH}\lib\x86\debug\lib-files
${MY_LIB_PATH}\lib\x86\release\lib-files
${MY_LIB_PATH}\lib\x64\debug\lib-files
${MY_LIB_PATH}\lib\x64\release\lib-files
What would a basic config file be like which makes CMake find_package know those? I expected it would be very simple because it just doesn't have much information to provide. But this page just make my head hurt.
Sorry, I decided to copy the source code around so I don't really know which answer should be accepted.
Don't write a config yourself; use CMake's export command. It's too broad to cover here in its entirety, but here's a modified example from one of my projects:
install(TARGETS
your_target
EXPORT YourPackageConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
export(TARGETS
your_target
NAMESPACE YourPackage::
FILE "${CMAKE_CURRENT_BINARY_DIR}/YourPackageConfig.cmake"
)
install(EXPORT
YourPackageConfig
DESTINATION "${CMAKE_INSTALL_DATADIR}/YourPackage/cmake"
NAMESPACE YourPackage::
)
This will create the config file for you, so other projects can use it via find_package.
find_package(YourPackage REQUIRED)
target_link_libraries(foo YouprPackage::your_target)
This handles the IMPORTED targets automatically, and also lets you embed compiler flags, include paths, library dependencies, and even which files are part of your interface (basically, anything that falls under the INTERFACE properties).
Put a "${libname}-config.cmake" in library's root.
Then add an IMPORTED target in that file.
There is a example for libprotobuf.
add_library(libprotobuf STATIC IMPORTED GLOBAL)
set_target_properties(libprotobuf PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/prebuilt/android/${ANDROID_ABI}/libprotobuf.a"
IMPORTED_LINK_INTERFACE_LIBRARIES "${ZLIB_LIBRARIES};${CMAKE_THREAD_LIBS_INIT}"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/src")
Set Env or CMake variable "${libname}_DIR" to "${MY_LIB_PATH}"
Use it.
find_package(${libname})
#.......
target_link_libraries(main ${libname})
Maybe this older doc could be a tad more lightweight. And there is also this tutorial or this other one. The last one is perhaps the simplest.
Hope to not have added more pain :-)
Following the docs should give something roughly like the following (supposing your library is mylib):
MyLib/MyLibConfig.cmake.in
# - Config file for the MyLib package
# It defines the following variables
# MYLIB_INCLUDE_DIRS - include directories for MyLib
# MYLIB_LIBRARIES - libraries to link against
# MYLIB_EXECUTABLE - the bar executable
# Compute paths
get_filename_component(MYLIB_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
set(MYLIB_INCLUDE_DIRS "#CONF_INCLUDE_DIRS#")
# Our library dependencies (contains definitions for IMPORTED targets)
if(NOT TARGET mylib AND NOT MyLib_BINARY_DIR)
include("${MYLIB_CMAKE_DIR}/MyLibTargets.cmake")
endif()
# These are IMPORTED targets created by MyLibTargets.cmake
set(MYLIB_LIBRARIES mylib)
MyLib/MyLibConfigVersion.cmake.in
set(PACKAGE_VERSION "#MYLIB_VERSION#")
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
main MyLib/CMakeLists.txt
...
set(MYLIB_MAJOR_VERSION 0)
set(MYLIB_MINOR_VERSION 1)
set(MYLIB_PATCH_VERSION 0)
set(MYLIB_VERSION
${MYLIB_MAJOR_VERSION}.${MYLIB_MINOR_VERSION}.${MYLIB_PATCH_VERSION})
...
add_library(mylib SHARED mylib.c ...)
...
install(TARGETS mylib
# IMPORTANT: Add the mylib library to the "export-set"
EXPORT MyLibTargets
RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib
PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/mylib"
COMPONENT dev)
...
# The interesting stuff goes here
# ===============================
# Add all targets to the build-tree export set
export(TARGETS mylib
FILE "${PROJECT_BINARY_DIR}/MyLibTargets.cmake")
# Export the package for use from the build-tree
# (this registers the build-tree with a global CMake-registry)
export(PACKAGE MyLib)
# Create the MyLibConfig.cmake and MyLibConfigVersion files
file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}"
"${INSTALL_INCLUDE_DIR}")
# ... for the build tree
set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
configure_file(MyLibConfig.cmake.in
"${PROJECT_BINARY_DIR}/MyLibConfig.cmake" #ONLY)
# ... for the install tree
set(CONF_INCLUDE_DIRS "\${MYLIB_CMAKE_DIR}/${REL_INCLUDE_DIR}")
configure_file(MyLibConfig.cmake.in
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/MyLibConfig.cmake" #ONLY)
# ... for both
configure_file(MyLibConfigVersion.cmake.in
"${PROJECT_BINARY_DIR}/MyLibConfigVersion.cmake" #ONLY)
# Install the MyLibConfig.cmake and MyLibConfigVersion.cmake
install(FILES
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/MyLibConfig.cmake"
"${PROJECT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
# Install the export set for use with the install-tree
install(EXPORT MyLibTargets DESTINATION
"${INSTALL_CMAKE_DIR}" COMPONENT dev)
Related
I am trying to import a third party package into my project. So I've been following:
https://cliutils.gitlab.io/modern-cmake/chapters/install/installing.html
But this fails with:
/tmp/top-level/bin/extern/MyLib
CMake Error at bin/extern/MyLib/MyLibConfig.cmake:12 (include):
include could not find load file:
/tmp/top-level/bin/extern/MyLib/MyLibTargets.cmake
Call Stack (most recent call first):
CMakeLists.txt:6 (find_package)
What am I missing from the documentation ? For reference, my top level cmakelists.txt is:
cmake_minimum_required(VERSION 3.18)
project(top-level)
add_subdirectory(extern)
find_package(MyLib CONFIG REQUIRED HINTS
${CMAKE_CURRENT_BINARY_DIR}/extern/MyLib)
And the cmakelists.txt file for 'MyLib' is:
cmake_minimum_required(VERSION 3.18)
project(MyLib VERSION 1.0 LANGUAGES C)
add_library(MyLib mylib.c)
add_library(MyLib::MyLib ALIAS MyLib)
install(
TARGETS MyLib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES
DESTINATION include)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
MyLibConfigVersion.cmake
VERSION ${PACKAGE_VERSION}
COMPATIBILITY AnyNewerVersion)
install(
EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib)
configure_file(MyLibConfig.cmake.in MyLibConfig.cmake #ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATION lib/cmake/MyLib)
The error message is self-explanatory:
You use script MyLibConfig.cmake from the build directory, and this script attempts to load the script MyLibTargets.cmake created by install(EXPORT MyLibTargets).
But the latter script is intended to work only after the project will be installed, it cannot work while the project is being built.
Actually, the whole call find_package(MyLib) is not needed in that situation:
since current project builds MyLib, the target MyLib::MyLib is already accessible for you.
If you want to make your top-level project to be flexible, so it would work both in cases MyLib is already installed or just being built, then you could use find_package conditionally:
cmake_minimum_required(VERSION 3.18)
project(top-level)
# This project could be built as standalone.
# In that case 'MyLib' is assumed to be already installed.
#
# Also, this project could work as a subproject of some other project,
# which also builds `MyLib` via 'add_subdirectory(MyLib)'.
if(NOT TARGET MyLib::MyLib)
find_package(MyLib CONFIG REQUIRED)
endif()
# ... use MyLib via 'MyLib::MyLib' target.
Alternatively, you may write MyLibConfig.cmake script in a manner, which allows it to be used even if MyLib is currently being built.
if(TARGET MyLib::MyLib)
return()
endif()
# ... usual content of the config file.
In that case, CMakeLists.txt for the root project could be simplified:
cmake_minimum_required(VERSION 3.18)
project(top-level)
# Normal use case is that 'MyLib' is already installed.
# But the project could work as a subproject in other scenarios.
#
# In those scenarios, a parent project should care about
# 'find_package' to work.
find_package(MyLib CONFIG REQUIRED)
# ... use MyLib via 'MyLib::MyLib' target.
The usage of the project in case of 'MyLib' being built could be as follows:
- CMakeLists.txt (outer)
- MyLib
- CMakeLists.txt (MyLib)
- top_level
- CMakeLists.txt ("top-level")
Outer CMakeLists.txt:
cmake_minimum_required(VERSION 3.18)
project(outer)
add_subdirectory(MyLib)
# Help inner project to find config file for MyLib.
#
# Here we use *internal* knowledge of MyLib project,
# that it generates 'MyLibConfig.cmake' directly in its build directory.
#
# Note: find_package expects 'XXX_DIR' variable to be CACHE one.
set(MyLib_DIR "${CMAKE_CURRENT_BINARY_DIR}/MyLib"
CACHE INTERNAL "Directory with MyLibConfig.cmake"
)
add_subdirectory(top_level)
I have a library foo which depends on bar. bar provides a nice barConfig.cmake upon installation.
cmake_minimum_required(VERSION 3.7)
project(foo VERSION 1.0)
find_package(bar)
add_library(foo SHARED foo.c)
target_link_libraries(foo PUBLIC bar)
install(TARGETS foo EXPORT fooTargets LIBRARY DESTINATION lib)
export(EXPORT fooTargets FILE fooTargets.cmake)
install(EXPORT fooTargets FILE fooTargets.cmake DESTINATION lib/cmake)
This works great. We simply apt install bar-dev then compile foo.
But since these packages are both developed by us and are related, my team would like to develop them in the same IDE session and compile them at the same time. I want to allow that, but I don't want to change the fact that these are already deployed as seperate packages and can be built independently.
if (DIRECTORY_EXISTS ../bar)
set(BAR_NO_INSTALL ON)
set(BAR_SKIP_TESTS ON)
add_subdirectory(../bar ${CMAKE_CURRENT_BINARY_DIR}/bar)
else()
find_package(bar)
endif()
If ../bar/ exists, then this results in:
CMake Error: install(EXPORT "fooTargets" ...) includes target "foo" which
requires target "bar" that is not in the export set.
How can I prevent the need to export bar in the foo package?
I'm trying to figure out why find_package(bar) works, but add_subdirectory(bar) doesn't. barConfig.cmake via barTargets.cmake defines bar like so:
add_library(bar SHARED IMPORTED)
set_property(TARGET bar APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
set_target_properties(bar PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "../bar/include"
IMPORTED_LOCATION_NOCONFIG "build/libbar.so.1.0.13574"
IMPORTED_SONAME_NOCONFIG "libbar.so.1"
)
My guess was the IMPORTED property that is used when bar is created in the autogenerated barConfig.cmake. I tried adding setting property (below), but saw no differences:
set_target_properties(bar PROPERTIES IMPORTED TRUE)
fooConfig.cmake does include the following which creates target bar (not bar::bar).
find_depdendency(bar)
I tried the following aliasing, hoping that an alias' inability to be exported would help me. But it still expects me to export bar.
add_library(barImported ALIAS bar)
target_link_libraries(foo PUBLIC barImported)
I tried linking only the build interface, hoping that bar would not need to be exported when installed. But that also had no effect.
target_link_libraries(foo PUBLIC $<BUILD_INTERFACE:bar>)
How can I prevent the need to export bar in the foo package?
You can't, at least not without hacks (see below).
I'm trying to figure out why find_package(bar) works, but add_subdirectory(bar) doesn't. barConfig.cmake via barTargets.cmake defines bar like so:
Because in one case, the target is imported (which can only be set on creation) and in the other case, the target is normal. Normal targets must be installed if normal targets that depend on them are installed.
The semantic issue with add_subdirectory is that CMake believes that any code you add this way is first-party. I have found that add_subdirectory and its relative FetchContent are more trouble than they're worth for installed dependencies. They're useful for build-or-test-only dependencies that do not need to be shipped.
One way to work around this is to use the COMPONENT / NAMELINK_COMPONENT features of the install() command. Then you would set the CPACK_COMPONENTS_ALL variable to include only the components from foo and not those from bar. When specifying components, you should avoid generic names like development or runtime, and instead prefix those names with the project name: foo_development, bar_runtime, etc.
Unfortunately, this does not affect the cmake --build . --target install command. You'll have to go through cmake --install . --component <comp> or CPack.
A hack is to add the bar dependency to foo as $<BUILD_INTERFACE:...> and then re-attach it to foo in fooConfig.cmake after include-ing your generated target export file and finding bar via find_dependency.
Here's the solution I came up with. This is for an arbitrary library foo (dynamic and static versions plus tests) which could also be a dependency in the same manner that I import bar:
cmake_minimum_required(VERSION 3.7)
include(GNUInstallDirs)
project(foo VERSION 1.0)
# Options set to
# ON explicitly for build machines,
# OFF by default for developers who build dependencies inline and only build for local use
option(FOO_EXPORT_TARGETS "Generate fooConfig.cmake for packaging." OFF)
option(FOO_BUILD_TESTS "Build and run tests. Default off for aircraft. Turn on in your IDE if developing foo" OFF)
# This is best implemented as a macro/function from an included cmake
set(BAR_ROOT "/noexist" CACHE PATH "Location of BAR for in-source building")
if (DIRECTORY_EXISTS "${BAR_ROOT}")
add_subdirectory(${BAR_ROOT})
else()
find_package(bar)
endif()
set(fooSources foo.c)
set(fooPublicHeaders include/foo.h)
add_library(fooObjects OBJECT ${fooSources})
target_include_directories(fooObjects
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE
$<TARGET_PROPERTY:bar,INTERFACE_INCLUDE_DIRECTORIES>
)
add_library(foo SHARED $<TARGET_OBJECTS:fooObjects>)
add_library(fooStatic STATIC $<TARGET_OBJECTS:fooObjects>)
target_link_libraries(foo PUBLIC fooObjects bar)
target_link_libraries(fooStatic PUBLIC fooObjects barStatic)
set_target_properties(fooObjects foo fooStatic
PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
if (NOT FOO_EXPORT_TARGETS)
install(TARGETS foo LIBRARY DESTINATION lib)
else()
set_target_properties(foo fooStatic
PROPERTIES
VERSION ${foo_VERSION}
SOVERSION ${foo_VERSION_MAJOR}
PUBLIC_HEADER "${fooPublicHeaders}"
)
install(
TARGETS foo fooStatic fooObjects
EXPORT fooTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/foo
)
include(CMakePackageConfigHelpers)
set(configDir "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/foo-${foo_VERSION_MAJOR}")
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/fooConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/fooConfig.cmake"
INSTALL_DESTINATION ${configDir}
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/fooConfigVersion.cmake"
VERSION ${foo_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/fooConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/fooConfigVersion.cmake"
DESTINATION ${configDir}
)
export (EXPORT fooTargets FILE fooTargets.cmake)
install(EXPORT fooTargets FILE fooTargets.cmake DESTINATION ${configDir})
endif()
if (FOO_BUILD_TESTS)
add_executable(test_foo test.c)
target_link_libraries(test_foo foo)
enable_testing()
add_test(NAME test_foo COMMAND test_foo WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test)
endif()
With the following fooConfig.cmake.in:
include(CMakeFindDependencyMacro)
#PACKAGE_INIT#
find_dependency(bar)
if(NOT TARGET foo)
include("${CMAKE_CURRENT_LIST_DIR}/fooTargets.cmake")
endif()
The build machines create a debian package out of this. This is what the debian/rules files look like to ensure it is compiled without any in-source building, with packaging, and with tests:
#!/usr/bin/make -f
#export DH_VERBOSE = 1
%:
dh $#
override_dh_auto_configure:
dh_auto_configure -- \
-DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) \
-DFOO_BUILD_TESTS=ON \
-DFOO_EXPORT_TARGETS=ON \
-DBAR_ROOT=/noexist
When I use set(CMAKE_DEBUG_POSTFIX "d"), the build and install targets work as expected. But in the libfooTargets-debug.cmake file with the exported targets, there is a path to libfoo and not libfood.
I exported the targets like this:
install(TARGETS libfoo EXPORT libfoo-targets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)
install(EXPORT libfoo-targets FILE libfooTargets.cmake DESTINATION ${CMAKE_INSTALL_PREFIX})
which creates and installs libfooTargets.cmake and libfooTargets-debug.cmake when building in debug mode, and libfooTargets.cmake and libfooTargets-release.cmake when building in release mode.
Both libfooTargets-release.cmake and libfooTargets-debug.cmake reference the name without a postfix as:
list(APPEND _IMPORT_CHECK_FILES_FOR_libfoo "${_IMPORT_PREFIX}/lib/libfoo.lib" )
and thus a program linking against the debug target still uses the release-build library and I would need to install release and debug versions into different folders to be able to link against the debug target.
How can I get the exported targets to work with a debug postfix?
I could of course try to change the library name depending on CMAKE_RELEASE_TYPE or a CONFIGURATION generator expression, but this will probably break the multi-configuration features in MSVC and other IDEs supporting different targets and seems not to work in the sense of how the exported targets feature is meant to simplify and unify the build.
I suspect that the install(EXPORT ...) command somehow drops the CMAKE_DEBUG_POSTFIX or does not implement it for generating the libfooTargets-{release,debug}.cmake files, but possibly I overlooked how to make this variable visible to the generator of the exported targets or something like this.
All target code
cmake_minimum_required(VERSION 3.11.1)
project(foo)
include(CMakePackageConfigHelpers)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_DEBUG_POSTFIX "d")
# ...
add_library(libfoo STATIC somesource.cpp someheader.h)
target_include_directories(libfoo PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
target_link_libraries(libfoo
somelibrary
)
target_include_directories (libfoo PUBLIC
somelibrary_header_dirs
)
install(TARGETS libfoo EXPORT libfoo-targets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)
install(EXPORT libfoo-targets FILE libfooTargets.cmake DESTINATION ${CMAKE_INSTALL_PREFIX})
configure_package_config_file(libfooConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libfooConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libfooConfig.cmake DESTINATION ${CMAKE_INSTALL_PREFIX})
install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN "*.h")
The platform is a Windows 10 with cmake 3.11.1 and MSVC 2015. Of course the most general solution is probably the best one.
According to the documentation of the install command, you need to reference the configuration that you are interested in:
[...] If a CONFIGURATIONS option is given then the file will only be installed when one of the named configurations is installed. Additionally, the generated import file will reference only the matching target configurations. [...]
So, you need to add the CONFIGURATIONS option in both install commands and duplicate the commands for each configuration you want to install and export.
I have static library Foo, static library Bar that depends on Foo and executable Baz that depends on Bar.
Relevant sections from Foo CMakeLists.txt:
# Specifying files to copy during "make install" command.
install(TARGETS Foo EXPORT FooConfig
INCLUDES DESTINATION include
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
# Specifying config file that will be used to find a library using find_package().
install(EXPORT FooConfig
FILE FooConfig.cmake
NAMESPACE Foo::
DESTINATION lib/cmake/Foo)
export(TARGETS Foo
NAMESPACE Foo::
FILE FooConfig.cmake)
Relevant sections from Bar CMakeLists.txt:
# Specifying libraries that are required for build.
find_package(Foo REQUIRED)
# Specifying libraries to link to for the users of the library.
target_link_libraries(Bar PUBLIC Foo::Foo)
# Specifying files to copy during "make install" command.
install(TARGETS Bar EXPORT BarConfig
INCLUDES DESTINATION include
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
# Specifying config file that will be used to find a library using find_package().
install(EXPORT BarConfig
FILE BarConfig.cmake
NAMESPACE Bar::
DESTINATION lib/cmake/Bar)
export(TARGETS Bar
NAMESPACE Bar::
FILE BarConfig.cmake)
And finally Baz CmakeLists.txt:
find_package(Bar REQUIRED)
target_link_libraries(Baz PRIVATE Bar::Bar)
Now when building Baz I get:
CMake Error at CMakeLists.txt:19 (add_executable):
Target "Baz" links to target "Foo::Foo" but the
target was not found. Perhaps a find_package() call is missing for an
IMPORTED target, or an ALIAS target is missing?
So build finds Bar and correctly determines that it depends on Foo but can't find Foo. I have another test project that directly depends on Foo and it builds fine. How to fix this?
Unfortunately, BarConfig.cmake generated like this does not handle finding dependencies, I had to modify Bar CMakeLists.txt to this:
# Specifying files to copy during "make install" command.
install(TARGETS Bar EXPORT BarTargets
INCLUDES DESTINATION include
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
install(FILES CMake/BarConfig.cmake DESTINATION lib/cmake/Bar)
# Specifying config file that will be used to find a library using find_package().
install(EXPORT BarTargets
FILE BarTargets.cmake
NAMESPACE Bar::
DESTINATION lib/cmake/Bar)
export(TARGETS Bar
NAMESPACE Bar::
FILE BarTargets.cmake)
Then I created a file CMake/BarConfig.cmake with this:
include("${CMAKE_CURRENT_LIST_DIR}/BarTargets.cmake")
find_package(Foo REQUIRED)
Now this BarConfig.cmake gets installed globally and calls find_package that finds Foo.
I have the simplest possible c-library which builds and is packed using the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project (libfoo C)
add_library(foo SHARED impl.c)
target_link_libraries(foo)
install(TARGETS foo LIBRARY DESTINATION lib/)
install(FILES public_header.h DESTINATION include/libfoo)
set(CPACK_GENERATOR "TGZ")
include(CPack)
Working example is located here: https://github.com/bjarkef/cmake-simple/tree/master/libfoo
I execute mkdir -p build; (cd build/; cmake ../; make all package;) to build a .tar.gz package with the compiled shared library along with its public header file. This is all working fine.
Now I wish to modify the CMakeLists.txt to create the FooConfig.cmake and FooConfigVersion.cmake files needed for CMake find_package in a different project to find the foo library. How do I do this?
I have discovered I should used the CMakePackageConfigHelpers: configure_package_config_file and write_basic_package_version_file, and I should create a FooLibraryConfig.cmake.in file. However I cannot figure out how to put it all together.
Note that it is important the the resulting .cmake files only contains relative paths.
I have cmake module included in the top level CmakeList.txt:
# Generate and install package config files
include(PackageConfigInstall)
Within the generic PackageConfigInstall.cmake file, the config files are created from the cmake.in files, and installed. This module can be reused for other packages.
include(CMakePackageConfigHelpers)
# Generate package config cmake files
set(${PACKAGE_NAME}_LIBRARY_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PACKAGE_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX})
configure_package_config_file(${PACKAGE_NAME}-config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_DIR}/${PACKAGE_NAME}
PATH_VARS LIB_INSTALL_DIR INCLUDE_INSTALL_DIR APP_INCLUDE_INSTALL_DIR )
configure_file(${PACKAGE_NAME}-config-version.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}-config-version.cmake #ONLY)
# Install package config cmake files
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}-config-version.cmake
DESTINATION
${CMAKE_INSTALL_DIR}/${PACKAGE_NAME}
COMPONENT
devel
)
You'll need a package file for your library, such as your_lib-config.cmake.in, which will become your_lib-config.cmake. This will contain the include and library variables that can be used.
get_filename_component(YOUR_LIB_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
# flag required by CMakePackageConfigHelpers
#PACKAGE_INIT#
set_and_check(YOUR_LIB_INCLUDE_DIR #PACKAGE_YOUR_LIB_INCLUDE_INSTALL_DIR#/hal)
set_and_check(YOUR_LIB_LIBRARY #PACKAGE_LIB_INSTALL_DIR#/#CMAKE_STATIC_LIBRARY_PREFIX##PROJECT_NAME_LIB##CMAKE_STATIC_LIBRARY_SUFFIX#)
set_and_check(YOUR_LIB_LIBRARIES #PACKAGE_LIB_INSTALL_DIR#/#CMAKE_STATIC_LIBRARY_PREFIX##PROJECT_NAME_LIB##CMAKE_STATIC_LIBRARY_SUFFIX#)
You'll also want a config-version.cmake.in file like this:
set(PACKAGE_VERSION #PACKAGE_VERSION#)
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
There's quite a bit to the packaging scripts to get it all to work just right. I went through a lot of trial and error to finally get something that works on different targets (both linux server and embedded target). I might have left something out, so please just comment and I'll update answer.