Code

CMake: EXCLUDE_FROM_ALL vs. EXCLUDE_FROM_DEFAULT_BUILD

Posted on

Getting EXCLUDE_FROM_ALL behaviour in Visual Studio

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:

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 and target_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 the EXCLUDE_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 sets EXCLUDE_FROM_DEFAULT_BUILD to FALSE 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!