Understanding the CMake `COMPONENT` keyword in the `install` command

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 CMake install 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 CMake find_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:

CppCon 2019: Deep CMake For Library Authors - Crascit

Tom reply:

I think my confusion stemmed from the COMPONENT keyword in the install command and the COMPONENTS keyword in the find_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 using cmake --install build --component <component>. One hitch I found was you can only install one component at a time and the Unspecified 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 that cmake --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 COMPONENTs to a CMake library so that I can list them in the find_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 the install 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! :slight_smile:

Thanks!

Tom

2 Likes

I haven’t had a chance to read it thoroughly myself yet, but I cleared the spam flag (probably added automatically due to length and/or link count).

Cc: @kyle.edwards @craig.scott

1 Like

I also got confused by the COMPONENTS and COMPONENT keywords and their relationship.

Forget the stuff I mentioned about find_package. As Craig wrote correctly the COMPONENT keyword in the install command only effects installing the specific components / targets but has no implicit effect on find_package or how it uses components.

The COMPONENTS keyword of find_package is just some kind of hint for the underlying export file and it is up to that file to decide what to do if a component is given to the find_package command for the specific library.

1 Like

Thank you for following up @dbahadir. It does seem like something others might trip up on so hopefully this will be of use to other readers.

The example I’ve included above (and this Stack Overflow question https://stackoverflow.com/q/54702582/1947066 + repo (https://github.com/markeastwood82/nomnoms) are useful references too.

1 Like

@tomhh I am wondering about the variants to create component libraries.

see GitHub - ClausKlein/cmake-example-component-lib: How to create a component library like boost

I am still not sure what is the right way?

Hi @ClausKlein,

I don’t have time to look into it right now sorry but this example might prove useful to puzzle out how to get things working - cmake-examples/examples/more/components at main · pr0g/cmake-examples · GitHub

Let me know if that’s any help and I’ll try have a look at the link you shared this evening,

Cheers!

Tom

Hi @ClausKlein,

Was that any help at all? I have a PR open with nomnoms too but it doesn’t seem like that repo is maintained at all. It seems like your PR has conflicting changes, might be worth looking at my fork too.

Cheers,

Tom

Thanks, it works now. (And is much simpler and clear)

see nomnoms/CMakeLists.txt at develop · ClausKlein/nomnoms · GitHub

I am sorry to say, but your version is still not complete:

CMake Error at CMakeLists.txt:5 (find_package):
  Could not find a configuration file for package "noms" that is compatible
  with requested version "1".

  The following configuration files were considered but not accepted:

    /Users/clausklein/Workspace/cpp/nomnoms/noms/stage/lib/cmake/noms/noms-config.cmake, version: unknown

-- Configuring incomplete, errors occurred!
Claus-iMac:noms clausklein$ git diff
diff --git a/munch/CMakeLists.txt b/munch/CMakeLists.txt
index 5c4a23b..e7e28b4 100644
--- a/munch/CMakeLists.txt
+++ b/munch/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 
 project(munch)
 
-find_package(noms REQUIRED CONFIG COMPONENTS veg fruit)
+find_package(noms 1 REQUIRED CONFIG COMPONENTS veg fruit)
 
 add_executable(${PROJECT_NAME})
 target_sources(${PROJECT_NAME} PRIVATE src/main.cpp)
Claus-iMac:noms clausklein$ tree stage/
stage/
├── include
│   └── noms
│       ├── fruit
│       │   └── apple.h
│       └── veg
│           └── asparagus.h
└── lib
    ├── cmake
    │   └── noms
    │       ├── noms-config.cmake
    │       ├── noms-fruit-config-debug.cmake
    │       ├── noms-fruit-config.cmake
    │       ├── noms-veg-config-debug.cmake
    │       └── noms-veg-config.cmake
    ├── libfruit.a
    └── libveg.a

7 directories, 9 files
Claus-iMac:noms clausklein$ 

IMHO the installation should be like this:

Claus-iMac:noms clausklein$ tree stage/
stage/
├── include
│   └── noms
│       ├── fruit
│       │   └── apple.h
│       └── veg
│           └── asparagus.h
└── lib
    ├── cmake
    │   └── noms
    │       ├── noms-config-version.cmake
    │       ├── noms-config.cmake
    │       ├── noms-fruit-targets-debug.cmake
    │       ├── noms-fruit-targets.cmake
    │       ├── noms-targets-debug.cmake
    │       ├── noms-targets.cmake
    │       ├── noms-veg-targets-debug.cmake
    │       └── noms-veg-targets.cmake
    └── libfruit.a

7 directories, 11 files
Claus-iMac:noms clausklein$ 

Hi @ClausKlein,

Ah okay, I don’t think I’d included versioning. You may well be right that what you’ve suggested is better (though I guess with so many things there are multiple ways of doing it, some no doubt better than others! :stuck_out_tongue:)

One thing I don’t understand from your last folder structure is libveg.a seems to be missing? Is that just because you hadn’t built it that time?

Thanks,

Tom

I changed libveg to an header only library

Ah okay got you :+1: