Specifying allowed configuration types

Specifying the allowed configuration types is tricky. People are confused by the variables CMAKE_CONFIGURATION_TYPES and CMAKE_BUILD_TYPE. Many don’t know about the usefulness of the STRINGS cache property.

One approach for reusable code is presented at https://www.kitware.com/cmake-and-the-default-build-type/

Another approach is the function I wrote:

function(allow_configurations)
  set(configs ${ARGN})
  
  if(DEFINED ENV{CMAKE_CONFIGURATION_TYPES})
    set(configs $ENV{CMAKE_CONFIGURATION_TYPES})
  endif()

  if(NOT configs)
    message(FATAL_ERROR "No configuratons given.")
  endif()

  get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
  if(is_multi_config)
    set(CMAKE_CONFIGURATION_TYPES ${configs})
    return()
  endif()

  set(default "") # list(GET configs 0 default) ?
  list(JOIN configs ", " help)

  if(DEFINED ENV{CMAKE_BUILD_TYPE})
    set(default $ENV{CMAKE_BUILD_TYPE})
  endif()

  set(CMAKE_BUILD_TYPE "${default}" CACHE STRING "Choose the type of build (${help}).")
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${configs}")

  if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE IN_LIST configs)
    message(FATAL_ERROR "CMAKE_BUILD_TYPE must be one of ${help}")
  endif()
endfunction()

I think it would be desirable that CMake provides a way to set the allowed configuration types.
Since both CMAKE_CONFIGURATION_TYPES and CMAKE_BUILD_TYPE are initialized in the project() command, why not integrate this logic there?

This would allow project authors to write:

project(MyProject
  CONFIGURATIONS Debug Release
  LANGUAGES CXX Swift
  VERSION 1.0
  )

And integrators would still be able to override the configurations with the CMAKE_CONFIGURATION_TYPES environment variable.

Thoughts?

I used to show how to restrict the configuration types in my Professional CMake book, but I now consider that to be an anti-pattern. Users should have the freedom to define and use whatever configurations they want. There are use cases where a user might want to create their own custom configuration, and if a project tries to restrict them to only a predefined set of configurations, it can be an annoying and unnecessary thing to work around.

It might make sense to initialize a pre-defined set of configurations for a multi-config generator if CMAKE_CONFIGURATION_TYPES isn’t set yet. This could be useful in company settings, for example. The case for single-config generators is much weaker, since you have to select a configuration by the time you hit the first project() call on the first run. There’s little point offering initialization of initial configurations for this because the user will always have to have made a choice before the first run. With multi-config generators, they don’t make that choice until build time, so that use case still makes sense.

The STRINGS cache variable property initially looks interesting, but in practice, I’m finding it increasingly less useful. If a project wants to make it easy for users to switch the configuration used, then they’re better off supporting multi-config generators. That’s a much better end user experience. It avoids having to re-run CMake each time you want to switch, you keep built artefacts for each config, so switching configs doesn’t rebuild the world, etc. Getting a bit off in the weeds, but I’m trying to highlight that we’re better off spending effort moving people to multi-config generators than on adding support for config selection logic for single-config generators.

2 Likes

Just to nitpick: in your function you’re using regular set(), which will just set that variable in the scope of the function. You probably want to use set(CACHE) and/or PARENT_SCOPE.

Can you please elaborate ?
The buildsystem may be tweaked to work for a known set of buildtypes, e.g. by setting compile flags or definitions for them, so with other buildtypes it may not work correctly.

How should “users” use whatever configuration they want ?

In such cases, it would then be up to the user to sort out any issues with their own custom build type. They might want a special type of debug build, for example, or maybe they want to use a particular sort of profiling method. Maybe they want to build with a very specific set of warning and optimisation flags. All of this could be done in a variety of ways, not just with a custom configuration, but an advantage of a custom configuration is that with a multi-config generator, they can easily switch between different configurations when they need to do different things with the same project. It makes doing comparisons between option sets easier.

If they extend the build system in such a way, it should be no problem for them to update the list of selectable configuration types.

Currently, the predefined list is questionable anyway:

  • getting debug symbols for a release build is more like an option, not a build type. Yes, they also use different optimization levels but that’s also a bit weird.
  • does anyone actually use the minsizerel build type?

Not always. They might be pulling in third party dependencies and want to use their custom configuration with those too. Trying to modify third party code just to use a custom configuration would be much less straightforward and would frequently be enough to make developers give up.

It’s definitely used by embedded folks (I have at least one consulting client where this is the case). The binary size requirements can be quite stringent, and MinSizeRel is squarely aimed at that scenario. That’s not to say that it couldn’t be handled by a different abstraction, but you could argue that for pretty much everything that would be different between different configuration types.