Should I modify CMAKE_MODULE_PATH in PackageConfig.cmake - cmake

I am writing a MyPackageConfig file for my project with exported targets so that other projects can easily find MyPackage and it's dependencies. It looks like this:
include(CMakeFindDependencyMacro)
find_dependency(LIB1_WITHOUT_CMAKE_CONFIG)
find_dependency(LIB2_WITH_CMAKE_CONFIG)
include (Some/Install/Dir/MyPackageTargets.cmake)
I am wondering if it is smart to add the following lines to the MyPackageConfig.cmake before the find_dependency calls
# Find target dependencies
# Allows packages linking with MyPackage to use the find modules that
# MyPackage used to find it's dependencies. Since this path is appended to
# the existing module path, the calling package's module path will take
# precedence
list(APPEND CMAKE_MODULE_PATH #CMAKE_INSTALL_PREFIX#/lib/cmake/MyPackage/modules)
# Allows packages linking with MyPackage to find MyPacakge's dependencies if
# they don't already have them. Since this path (or these paths) are
# appended to the existing prefix path, the calling package's prefix
# path will take precedence
list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
find_dependency(LIB1_WITHOUT_CMAKE_CONFIG)
find_dependency(LIB2_WITH_CMAKE_CONFIG)
Good idea? No?
Longer explanation of my rationale:
How does YourPackage that uses MyPackage find LIB1?
(i). You could write your own FindLIB1.cmake but that's duplication of effort
(ii). I could install my FindLIB1.cmake alongside my MyPackageConfig.cmake in a Modules dir. But you will have to include this path in your module path.
My suggestion: Add a line before find_dependency(LIB1_WITHOUT_CMAKE_CONFIG) modifying the module path like so:
list(APPEND CMAKE_MODULE_PATH #CMAKE_INSTALL_PREFIX#/lib/cmake/mstk/modules)
This will ensure that if you have a FindLIB1.cmake, it will be used but if you don't mine will be found and used.
How do you know where the LIB1 and LIB2 reside (including LIB2's Config file)?
By adding the line
list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
I tell your package where I searched and found my dependencies (but only if you didn't already have them in the CMAKE_PREFIX_PATH you specified)

Yes, you may change variables like CMAKE_MODULE_PATH or CMAKE_PREFIX_PATH for the purpose of your config script.
Any "good" project should be prepared to prepending/appending CMAKE_PREFIX_PATH, because this variable could normally be set by a user (when call cmake). As for CMAKE_MODULE_PATH, module's names in different directories are rarely conflicted.
Some hints:
You may restore the variables at the end of your script. Such way you won't affect the calling code when changing the variables:
# Store old value of the variable
set(old_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
# Change variable, use it, ...
# ...
# Restore the variable at the end
set(CMAKE_MODULE_PATH ${old_CMAKE_MODULE_PATH})
Note, however, that find_dependency returns from the script if an (inner) package not found, so restoring the variable won't trigger in that case. But usually find_package() is called with REQUIRED keyword, so failing to find the inner package would be fatal.
Instead of changing CMAKE_PREFIX_PATH you may set other variables which affect only specific find script and doesn't affect others. E.g many find scripts use XXX_ROOT variables for hint about location.
For find config script itself, you may use PATHS or HINTS options of find_package():
# Was:
#- list(APPEND CMAKE_PREFIX_PATH #CMAKE_PREFIX_PATH#)
#- find_dependency(LIB2_WITH_CMAKE_CONFIG)
# Replace with:
find_dependency(LIB2_WITH_CMAKE_CONFIG
PATHS #CMAKE_PREFIX_PATH# # Where to find config script
NO_DEFAULT_PATH # Do not search other places
)

Related

Why no 'install' rules generated by cmake install()?

I'm running cmake v 3.18.0 on ubuntu 20.04
My install() command doesn't generate any install rules within the generated Makfile. CMakeLists.txt looks like this:
# Static library
add_library(mbaux
STATIC
mb_cheb.c mb_delaun.c mb_intersectgrid.c mb_readwritegrd.c
mb_surface.c mb_track.c mb_truecont.c mb_zgrid.c)
target_include_directories(mbaux
PUBLIC
.
${X11_INCLUDE_DIR}
${GMT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/mbio)
# Static library
add_library(mbxgr
STATIC
mb_xgraphics.c)
target_include_directories(mbxgr
PUBLIC
.
${X11_INCLUDE_DIR}
${GMT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/mbio)
# Install
message("set up mbaux install targets")
install(TARGETS mbaux DESTINATION cmake-example/lib)
install(TARGETS mbxgr DESTINATION cmake-example/lib)
Yet when I look in the generated Makefile I see my message "set up mbaux install targets" but there is no reference to the destination 'cmake-example/lib', and no specific 'install' target that I can see.
Thanks!
Just because you seem new to CMake, a couple words of advice re: install rules...
First, avoid hard-coding destinations. You always want to include(GNUInstallDirs) (even on Windows). This module defines a number of standard cache variables for configuring installation destinations.
See the documentation for the full list, but the following are the most relevant here:
CMAKE_INSTALL_INCLUDEDIR - the path below the installation prefix where the include structure should exist. Typically, and by default, include.
CMAKE_INSTALL_LIBDIR - the path below the installation prefix where libraries should be stored. Typically, and by default, lib. Often overridden on multiarch systems to include the target architecture, such as lib/aarch64-linux-gnu.
CMAKE_INSTALL_DATADIR - the path below the installation prefix where package-specific data should be stored. Typically, and by default, share.
In CMake 3.14+ these are used as the default destinations for install(TARGETS). This should be all you need:
include(GNUInstallDirs)
install(
TARGETS mbaux mbxgr ...
EXPORT mb-targets
RUNTIME COMPONENT mb-runtime
LIBRARY COMPONENT mb-runtime
NAMELINK_COMPONENT mb-development
ARCHIVE COMPONENT mb-development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
Don't forget to guard your include paths with $<BUILD_INTERFACE:...> in the main build (to allow INCLUDES DESTINATION above to overwrite them):
target_include_directories(
mbaux
PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/mbio>"
)
You almost certainly do not want to use the ${<PACKAGE_NAME>_INCLUDE_DIRS} variables here, either. Instead use target_link_libraries with the relevant imported targets; this will set up include paths automatically.
target_link_libraries(mbaux PRIVATE X11::X11) # likely similar for GMT/GDAL
When you are required to specify a destination, you generally want to use the TYPE argument instead of DESTINATION. If you must use DESTINATION, use a project-specific cache variable as its argument; for example:
# will install headers to ${CMAKE_INSTALL_INCLUDEDIR}/**/*.h
# without mbio prefix, remove the trailing / to include mbio
# in the path
install(DIRECTORY "${PROJECT_SOURCE_DIR}/src/mbio/"
TYPE INCLUDE
COMPONENT mb-development
FILES_MATCHING PATTERN "*.h")
set(MB_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/cmake/mb"
CACHE STRING "Installation directory for CMake package files")
install(EXPORT mb-targets
DESTINATION "${MB_INSTALL_CMAKEDIR}"
NAMESPACE mb::
FILE mb-targets.cmake)
Finally, you should never read or set CMAKE_INSTALL_PREFIX from within the CMakeLists.txt file. Instead you should rely on relative paths (as done above) or the $<INSTALL_PREFIX> generator expression (in exceptional circumstances) to avoid making your installation script non-relocatable.
As pointed out by Stephen Newell, cmake spreads rules and logic across multiple files - and I now see where they are (in this case, cmake_install.cmake)

cpack conditonal behaviour based on generator

I have a build which is currently set up with steps as follows:
cmake
make
cpack -G TGZ
cpack -G RPM
I now have a problem in that there are files I wish to include in the RPM but not the tarball. Is there a way to make the install command conditional according to the generator used?
The simple and obvious way is wrong:
if (NOT ${PACKAGE_TYPE} STREQUAL "TGZ")
message("HELLO ${PACKAGE_TYPE}")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/foobar DESTINATION "/usr/lib" COMPONENT core RENAME "/usr/lib/only-install-me-for-RPM")
endif()
I believe it is wrong because the configure stage (running cmake) evaluates the conditional but cpack does not.
I do not want two builds as the install stage is the only part different. I do want more than one kind of installation package.
Background
Why do I want to do such an odd thing? I can think of other legitimate reasons but in this case it is because of the introduction of /usr/lib/.build-id.
It is not possible to disable this behaviour from cmake (though it is possible in the .spec file see https://bugzilla.redhat.com/show_bug.cgi?id=1724153)
In RHEL8 rpmbuild installs files (actually links) in /usr/lib/.build-id which I have not specificed myself.
In order to persuade cmake to make /usr/lib relocatable I have to install a dummy file in /usr/lib - see https://gitlab.kitware.com/cmake/cmake/-/issues/20691
This is not necessary for the tarball.
Currently used CPack generator can be retrieved from CPACK_GENERATOR variable. But this meaning works only inside a script specified with CPACK_PROJECT_CONFIG_FILE variable. Inside CMakeLists.txt the variable CPACK_GENERATOR has other meaning.
Because install command can only be issued in CMakeLists.txt, this command cannot be made conditional (based on CPack generator). But you may assign a COMPONENT for this installation. This component can be excluded from the components list later.
CMakeLists.txt:
# Assign 'core_special' COMPONENT for installation
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/foobar DESTINATION "/usr/lib" COMPONENT core_special RENAME "/usr/lib/only-install-me-for-RPM")
# ...
# Set config script for CPack.
set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_SOURCE_DIR}/cpack_project_config.cmake")
cpack_project_config.cmake:
# Exclude component "core_special" for all CPack generators except TGZ.
if (NOT CPACK_GENERATOR STREQUAL "TGZ")
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "core_special")
endif()
# Need to set 'CMAKE_<GENERATOR>_COMPONENT_INSTALL' to ON, otherwise CPack ignores CPACK_COMPONENTS_ALL variable
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_RPM_COMPONENT_INSTALL ON)
# E.g create single archive/package from all components
# (other values - "IGNORE", "ONE_PER_GROUP" - will also work)
set(CPACK_COMPONENTS_GROUPING "ALL_COMPONENTS_IN_ONE")

How to execute CMake's default find module from my own find module with the same name?

For some reason I want to extend a find module (FindThreads in my case) to do some specific stuff if CMake's FindThreads fails. For the ordinary developer of other parts of our software system this should happen transparently, such that s/he could call find_package(Threads) as usual, so I don't want to give my own find module an exclusive name.
My idea is to add my own FindThreads.cmake in a folder contained in the CMAKE_MODULE_PATH. Because CMAKE_MODULE_PATH has priority find_package(Threads) will execute my own FindThreads.cmake.
But, inside my own FindThreads.cmake I need to execute find_package(Threads ...) such that it executes CMake's original FindThreads.cmake. How to do that?
If that fails my own FindThreads.cmake should try to add a specific Threads lib.
Unfortunately, there is no option NO_CMAKE_MODULE_PATH for find_package to fall back to CMake's own find modules and it seems not to be possible to pass a search path (for example ${CMAKE_ROOT}/Modules) to find_package to override CMAKE_MODULE_PATH.
Versions
CMake 3.5 on Linux (Ubuntu 16.04)
CMake 3.14 on Windows
You may set CMAKE_MODULE_PATH before find_package call and restore the variable after the call:
# Save old value of CMAKE_MODULE_PATH.
set(CMAKE_MODULE_PATH_OLD ${CMAKE_MODULE_PATH})
# Temporary replace CMAKE_MODULE_PATH
set(CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules")
# Call find_package() with specific CMAKE_MODULE_PATH set.
find_package(Threads ...)
# Restore CMAKE_MODULE_PATH
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH_OLD})
Note, that FindXXX.cmake is executed in the same scope as the caller code, so restoring CMAKE_MODULE_PATH variable is needed even if your script no longer calls find_package but simply returns.
Also, it is better to use some unique variable's name instead of CMAKE_MODULE_PATH_OLD: it is possible that caller already uses that variable for its own purposes. E.g. you may add some suffix to the variable's name which related to your organization or is unlikely to be used by others.

set PKG_CONFIG_PATH in cmake

I have built opencv locally and installed it to a local directory (not the system default ). opencv.pc is present under a folder pkgconfig in this local folder. How can I find this opencv.pc from cmake, because I want to link and include opencv files from my program.
pkg_search_module(<PREFIX> [REQUIRED] [QUIET] <MODULE> [<MODULE>]*)
does not have any parameter in which I can force the command to use a specific path (similar to HINTS with find_package()) and not the system default.
So basically .cmake works:
find_package(OpenCV REQUIRED HINTS "my/path/to/share/OpenCVConfig.cmake")
but I would like to use a opencv.pc located under my/path/to/pkgconfig/opencv.pc.
After doing some research and a hint from the #OlivierM, I found the answer.
Here are the steps:
Method I :
CMAKE_PREFIX_PATH can be set to find the .pc files
set(CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/libs/opencv-install")
Method II
A second method is to use the PKG_CONFIG_PATH, which is a system environment variable to look for .pc files.
set(ENV{PKG_CONFIG_PATH} "${CMAKE_SOURCE_DIR}/libs/opencv-install/lib/pkgconfig")
Irrespective of which method you use,
For old (traditional) CMake:
find_package(PkgConfig REQUIRED)
pkg_search_module(PKG_OPENCV REQUIRED opencv) # this looks for opencv.pc file
Please note that the PKG_OPENCV variable can be named anything. Whatever it is is named, its used as a prefix. For example if you name ABCD, then include directories will be ABCD_INCLUDE_DIRS
The variable PKG_OPENCV_INCLUDE_DIRS and PKG_OPENCV_LIBRARIES contains the header files (compile stage) and libraries (link stage) respectively.
One very important thing I noticed was that the variable PKG_OPENCV_LIBRARIES just provides the libraries and not the library path during the link stage. In order to use the library path as well in one command, one has to use
PKG_OPENCV_LDFLAGS
This variable contains the library path as well as all the libraries listed in the package config file.
for examaple:
include_directories(${PKG_OPENCV_INCLUDE_DIRS})
target_link_libraries (FINAL_BINARY ${PKG_OPENCV_LDFLAGS})
For modern CMake:
In modern CMake we don't want variables, we want targets.
find_package(PkgConfig REQUIRED)
# this looks for opencv.pc file and creates a new target
# IMPORTED_TARGET requires CMake >= 3.6.3
pkg_search_module(PKG_OPENCV REQUIRED IMPORTED_TARGET opencv)
All variables will still be created for backwards compatibility, but IMPORTED_TARGET will create a target you can use in your project which will automatically propagate all of its build and usage requirements:
target_link_libraries(my_proj PRIVATE PkgConfig::PKG_OPENCV)
You could set PKG_CONFIG_PATH with the CMake line:
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/my/path/to/pkgconfig")
I did this workaround in this file
Interestingly, it seems CMake 3.1 extends PKG_CONFIG_PATH with some CMake variable see: https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=3df51470
I would propose you to call cmake with custom PKG_CONFIG_PATH variable, like below:
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:my/path/to/pkgconfig cmake <some args>
Or can make PKG_CONFIG_PATH update to be permanent for whole bash session:
$ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:my/path/to/pkgconfig
$ cmake <some args>

How to pass variable to cpack?

I have a cmake project which one of the install targets is a collection of files. This files change depending on the configuration (Release, Debug...).
I would like to be able to install the files like so:
install(DIRECTORY $<TARGET_FILE_DIR:tgt>
DESTINATION bin
COMPONENT files)
But cmake does not support that. Generator variables do not apply to DIRECTORY. So I was wondering if there is a way to either save the directory somewhere. Either the cache or a file and then load it into cpack.
So I guess the question is how to pass a variable from cmake to cpack?
This is a rather late answer, but I happened upon this question trying to solve a somewhat different problem that could also be summarized as: "How do I pass a variable to CPack?" In my case, I was making this call from a customized version of CPackDeb.cmake copied to my workspace:
find_program(OPKG_CMD NAMES opkg-build HINTS "${OPKG_HINT}")
# ^^^^^^^^^^^^
# This is what I wanted to pass to CPack
I was setting OPKG_HINT in a file included from my top-level CMakeLists.txt, but it was not getting passed through to cpack; the above find_program() invocation was seeing an empty string for OPKG_HINT.
The solution turned out to be stupid simple: just prepend CPACK_ to the variable name!
If I do this in CMakeLists.txt:
set(CPACK_OPKG_HINT "${_sysroot_top}/aarch64-poky-linux/usr/bin")
then I can put this in my CPackDeb.cmake file and it works fine:
find_program(OPKG_CMD NAMES opkg-build HINTS "${CPACK_OPKG_HINT}")
Anyway, this wound up being a bit of an X-Y problem for the OP, but... if you really need to set a variable at CMake time in such a way that it's accessible to cpack, prefixing the variable name with CPACK_ seems to do the trick nicely...
The following setup work if you use a "single-configuration generators (such as make and Ninja)" and call CMake with
cmake -DCMAKE_BUILD_TYPE=Release <source_dir>
https://cmake.org/cmake/help/v3.0/variable/CMAKE_BUILD_TYPE.html
You can define the ${dir} variable in another way if you like.
IF (CMAKE_BUILD_TYPE STREQUAL "Release")
SET(dir release_dir)
ELSE()
SET(dir debug_dir)
ENDIF()
INSTALL(DIRECTORY ${dir} DESTINATION bin COMPONENT files)
Until now this seems to be the best answer (from someone on the cmake mail list)
install(DIRECTORY path/to/Debug/dir
DESTINATION bin
CONFIGURATIONS Debug
COMPONENT files
)
install(DIRECTORY path/to/Release/dir
DESTINATION bin
CONFIGURATIONS Release
COMPONENT files
)
CMake 3.5 supports generator expressions for the DIRECTORY arguments. See installing directories.