Code
CMake: EXCLUDE_FROM_ALL vs. EXCLUDE_FROM_DEFAULT_BUILD
Posted on
Table of Contents
add_library(target EXCLUDE_FROM_ALL ...)
is really convenient with Make-generators if you want to define libraries that should only be built if another target depends on them. This currently doesn’t work (well) for Visual Studio generators, but here’s a trick how to get this behaviour in Visual Studio as well…
(Written for CMake 3.6)
EXCLUDE_FROM_ALL
As written in the CMake docs, the EXCLUDE_FROM_ALL
target-property causes a target to be excluded from the “all” build-target, which is the default if make
is started without any args.
If used for static libraries (e.g. by using add_library(target EXCLUDE_FROM_ALL ...)
), the library will be built nevertheless if another target (library or executable) depends on it.
Like this, you can define a bunch of libraries (e.g. in your shared codebase) and only those that are actually needed will be built.
This reduces build times and makes it easier to handle component-dependencies.
I’m not sure if this use case is what the implementor of EXCLUDE_FROM_ALL
had in mind, but I find it practical like that.
EXCLUDE_FROM_DEFAULT_BUILD
Interestingly, there’s the EXCLUDE_FROM_DEFAULT_BUILD target property which on first sight seems to be somewhat redundant.
But it isn’t, it’s a property specific to the Visual Studio generator and only says that a target (i.e. a Visual Studio project) isn’t built when the Solution is build by pressing “Build Solution (F7)”.
The difference to EXCLUDE_FROM_ALL
: If a target is excluded from build like that, it won’t be built even if another target depends on it.
This doesn’t mean that EXCLUDE_FROM_ALL
is ineffective with Visual Studio: If you build the “ALL_BUILD” project, you’ll get the same behaviour as with Make.
While this works, I don’t like it: People are used to hitting F7 (or whatever the shortcut is) and the project should build.
The solution
My use case:
- A bunch of static libraries in the shared codebase where not every library is needed in every project
- A (CMake-) project consists of one or more executables
- Builds with Visual Studio and Make
Others seem to have wanted to do the same:
- https://gitlab.kitware.com/cmake/cmake/issues/12379
- http://public.kitware.com/pipermail/cmake/2011-August/045662.html
- http://public.kitware.com/pipermail/cmake/2013-February/053528.html
This is my solution for this problem:
# Override add_library() and set the "EXCLUDE_FROM_DEFAULT_BUILD" property to TRUE on all libraries that use "EXCLUDE_FROM_ALL".
function(add_library TARGET)
_add_library(${TARGET} ${ARGN})
cmake_parse_arguments(OPTS "EXCLUDE_FROM_ALL" "" "" ${ARGN})
if(OPTS_EXCLUDE_FROM_ALL)
set_target_properties(${TARGET} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE)
endif()
endfunction()
# Helper function to recursively set EXCLUDE_FROM_DEFAULT_BUILD to FALSE on all targets "TARGET" depends on
function(_include_in_build TARGET)
if(TARGET ${TARGET})
get_target_property(TYPE ${TARGET} TYPE)
if(TYPE MATCHES "INTERFACE")
get_target_property(DEPS ${TARGET} INTERFACE_LINK_LIBRARIES)
else()
set_target_properties(${TARGET} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD FALSE)
get_target_property(DEPS ${TARGET} LINK_LIBRARIES)
endif()
if(DEPS)
foreach(D ${DEPS})
_include_in_build(${D})
endforeach()
endif()
endif()
endfunction()
# Take executables as roots to run _include_in_build() on
function(target_link_libraries TARGET)
_target_link_libraries(${TARGET} ${ARGN})
get_target_property(_TYPE ${TARGET} TYPE)
if(_TYPE MATCHES "EXECUTABLE")
_include_in_build(${TARGET})
endif()
endfunction()
How does it work?
- First, a trick I like to use: Functions like
add_library
andtarget_link_libraries
can be “overridden” by just defining a new version. The original function is still available as e.g._add_library
or_target_link_libraries
. - For every library that is defined which has the
EXCLUDE_FROM_ALL
property set theEXCLUDE_FROM_DEFAULT_BUILD
property is set as well. - For every executable target on which
target_link_libraries()
is used, the function_include_in_build()
recursively setsEXCLUDE_FROM_DEFAULT_BUILD
toFALSE
on all targets the executable depends on. Imagine the executable as the root of a tree of dependencies which is traversed. - There’s special handling for “interface targets”, as they don’t have the
EXCLUDE_FROM_DEFAULT_BUILD
property.
Conclusion
While this works for my use case, I think a better way would be to have the ability to define “non-standalone” targets in CMake. Targets like this would not be built if no other target depends on them. Something to be discussed with the CMake devs…
That’s all, thanks for reading!