Hello there,
I recently had a number of questions around the CMake COMPONENT
keyword used in the CMake install
command and using COMPONENTS
in the CMake find_package
command. After watching excellent talks by both Craig Scott and Deniz Bahadir I reached out to them directly via e-mail and got some very helpful responses. In case anyone else might find this useful I’m including the conversations here so it’s not just for my benefit.
Tom question:
I am trying to understand what the
COMPONENT
keyword is in the CMakeinstall
command. I did a bunch of digging but can’t seem to find what it does. I wanted to use if for specifying components in the CMakefind_package
command, but I found it actually isn’t necessary at all and you can simply use careful naming of-config.cmake
files.
Craig Scott answer:
The COMPONENT keyword allows you to group installed things into separate “bags” that you can then use to refer to them later. These can be used to selectively pull in just a subset of available things into a package (e.g. with the “cmake --install --component ” command), or to create multiple packages based around those components. Some package generators also support a hierarchy of components through grouping, which is how you get things like optional install components in some GUI installers. Referring to the 5th edition of my book, Section 25.2 " Installing Targets " explains what components are and gives an example of a typical usage (starting near the bottom of page 324). In the next chapter on packaging, Section 26.2 " Components " covers how components in much more detail, covering how relationships between components can be expressed and how to use that for package generation.
For a more arm-chair coverage of this area, you might also want to watch my CppCon 2019 talk “Deep CMake For Library Authors”. The middle section talks a fair bit about install components and builds up an example with motivation for why you might put parts of a library in different components. My website has a link to that talk and accompanying slides here:
Tom reply:
I think my confusion stemmed from the
COMPONENT
keyword in theinstall
command and theCOMPONENTS
keyword in thefind_package
command. These appear to be entirely orthogonal concepts but I’d assumed they were tightly related.The thing I was interested in doing was what Qt and Boost do by allowing users to list the components they want in their
find_package
command. There’s another interesting talk called ‘Oh No! More Modern CMake’ that recommends this but unfortunately doesn’t show exactly how to set it up.In the example I linked in my first email I do now have things working and have locally experimented with the adding the
COMPONENT
keyword and usingcmake --install build --component <component>
. One hitch I found was you can only install one component at a time and theUnspecified
component can’t be installed at the same time as a more specific one (a common component as it were). Maybe there’s a workaround for this I’m not sure, or you just have to run the command multiple times.
Craig Scott reply:
Yes they are different things, but it would not be unusual for the package components to line up fairly closely to the install components. It would certainly make it easier to do so (for the person maintaining the project).
I used fairly poor wording in my initial reply (the result of various edits before sending the final version). The
cmake --install
command is meant only if you want to do a manual install of the project or individual components locally. You don’t need it for producing a package and shouldn’t try to do so. Define your package contents and install components within your project and let CPack take care of turning all that into a final package for you. Sorry if that wasn’t clear and it seems like I was suggesting thatcmake --install
should somehow be used within the project itself.I rarely find a need to use
cmake --install
. I don’t recall if you can provide the --component option more than once or give it a list of components. You could try it and see.Where possible, you should try to avoid having your project put things into the “Unspecified” component. That usually means you’ve got something being installed without any component specified. It often occurs when a third party dependency is brought into your build directly (e.g. with FetchContent) and that dependency doesn’t define install components. You can change the default component name just before you pull in that dependency though using the
CMAKE_INSTALL_DEFAULT_COMPONENT_NAME
variable (mentioned on page 325 of my book).
Deniz Bahadir’s response is harder to quote as he provided updated commands of an example CMakeLists.txt
file I provided. I’ll include those as-is for now in case it’s useful.
Tom question:
I am trying to understand how to add
COMPONENT
s to a CMake library so that I can list them in thefind_package
command of a consuming application and I can’t seem to find a clear idiomatic way of how to do this.Suppose I have CMakeLists.txt file that looks like this:
cmake_minimum_required(VERSION 3.15)
project(messages LANGUAGES CXX)
include(GNUInstallDirs)
add_library(hello)
target_sources(hello PRIVATE hello/hello.cpp)
target_include_directories(
hello PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/hello/include/>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
install(
TARGETS hello
EXPORT ${PROJECT_NAME}-config
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(
DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/hello/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
install(
EXPORT ${PROJECT_NAME}-config
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
It is a super simple project called
messages
with a single library called hello. I would like to make a COMPONENT called greetings so in my application, instead of just doing:
find_package(messages REQUIRED CONFIG)
...
target_link_libraries(
${PROJECT_NAME} PRIVATE messages::hello)
I can do this:
find_package(messages REQUIRED CONFIG greetings)
...
target_link_libraries(
${PROJECT_NAME} PRIVATE messages::hello)
But whenever I try and add any
COMPONENT
keyword to theinstall
commands listed above, it seems to have no effect at all.I’ve seen some examples where people have to manually create multiple
<project>-config
files but it seems to me there should be a simpler way.
Deniz reply:
...
install(
# Install the target `hello`.
TARGETS hello
# Associate `hello` with export file `messages-config.cmake`.
EXPORT ${PROJECT_NAME}-config
# Only install `hello` if it is statically linked!
ARCHIVE
# Install it here:
DESTINATION ${CMAKE_INSTALL_LIBDIR}
# Associate `hello` with a component `greetings`.
COMPONENT greetings
# If we do not want to install `hello` when using the component-less
# `find_package` call, we must uncomment the following line, too.
# EXCLUDE_FROM_ALL
)
install(
# Install the (public) headers of `hello`.
DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/hello/include/
# Install them here:
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
# Associate the (public) headers of `hello` with a component
`greetings`.
COMPONENT greetings
# If we do not want to install these headers when using the
component-less
# `find_package` call, we must uncomment the following line, too.
# EXCLUDE_FROM_ALL
)
# Install the export file:
install(
# Generate and install the export file `messages-config.cmake`.
EXPORT ${PROJECT_NAME}-config
# Install it here:
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
# Make sure all its targets will get the namespace `messages::`
prepended.
NAMESPACE ${PROJECT_NAME}::
# Optionally, we could add another component `conversation` which
depends
# on all the other components from the export file and would install
all
# of them.
COMPONENT conversation
)
Now, if you just want to install the
hello
target and its include
files you can do the following:
find_package(messages REQUIRED COMPONENTS greetings CONFIG)
...
target_link_libraries(${PROJECT_NAME} PRIVATE messages::hello)
If you want to install all components without explicitly mentioning all
of them, do the following:
find_package(messages REQUIRED COMPONENTS conversation CONFIG)
...
target_link_libraries(${PROJECT_NAME} PRIVATE messages::hello)
End
I have created an example application and library to demonstrate the effect I was after (ironically not yet actually using the COMPONENT
keyword) - cmake-examples/examples/more/components at main · pr0g/cmake-examples · GitHub.
I hope someone else found this exchange useful and a huge thank you to Craig and Deniz for taking the time to reply to me.
If anyone has anything more to add regarding this I’d be very grateful to hear!
Thanks!
Tom