How to share common cmake properties between exes?

My project is not large, but we do build about 100 executables. These executables come in a handful of different types: daemons, tools, tests, filters, etc. Usually, all the executable targets of a certain type share the same CMake properties, so I’d like to be able to default these once per type. But there doesn’t appear to be a good way to do this in CMake.

Today I do this with wrapper functions for add_executable, like add_daemon_executable, etc. But this doesn’t feel properly “modern” CMake. There is great debate in our group whether wrapping CMake functions like this is good software abstraction process, or if it represents shortcoming in the CMake system. I don’t want to set these properties globally, and the types of executables are not represented in the source tree – there’s not one interior node in the tree for “daemons”.

Seems like the “modern CMake target-based way” should be to have an “executables (plural)” kind of target that is a container of other executables, that can have properties set on it, which flow down to dependent executables, much like library targets. If you think of a CMake target as an object, this would be similar to having inheritance between objects. But I don’t see a way to do this with executables.

What’s the best practice for sharing common properties between executables of similar types?

CMake has no concept of a meta target that acts as a proxy for a set of other targets. I do the add_daemon_executable method myself (because I usually wrap up installation and export set management as well).

1 Like

Thanks Ben, this makes me feel better about our existing approach. I know just enough about “modern CMake” to feel that the add_daemon_executable wrapper isn’t the pure, clean way to do things, but I don’t know enough to suggest a better solution.

Someone much wiser than I privately recommended appending to the target, and not wrapping add_executable, so

add_executable(target, usual options)
mark_executable_daemon(target)

I think I like this approach better, even if it is a bit more verbose.

Possibly using INTERFACE library can help:

add_library (daemon INTERFACE)
target_compile_options (daemon INTERFACE ...)
target_link_options (daemon INTERFACE ...)

add_executable (my_target ...)
target_link_libraries (my_target PRIVATE daemon)
1 Like

That could also work if all of your daemon bits are propagated via target_link_libraries. Things like rpath settings are not set this way, so things like that would need a function somewhere. Wrapping it in $<BUILD_INTERFACE:daemon> can help to avoid having to export the internal target as well.

1 Like

Thanks, Marc! This approach feels more “modern CMake” to me, but I’m not sure I want to propose it for my project, as it seems a little hacky, and violating the principle of least surprise. What if we added INTERFACE executable targets to CMake that could carry and propagate properties like libraries, but never generated executables? Maybe that would more clearly convey intent?

This is exactly that: an INTERFACE library do not generate anything.

I have used the secondary function approach ( mark_executable_daemon ) before and it works really well if you are shipping these components to consumers. In this case they will need to compose your requirements and what ever ones they have.

The INTERFACE approach such as:

add_executable(target, usual options)
target_link_libraries(target PRIVATE daemon_reqs)

Also does this type of composition ( as mentioned by Marc ) and if you can formulate your needs with that approach I would do so. People are used to seeing target_link_libraries and they should have less confusion compared to custom functions.

1 Like