Correct way to specify multiple build configurations for single config generators?

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Correct way to specify multiple build configurations for single config generators?

Zaak Beekman
I have been reading the *excellent* book "Professional CMake". The author, Craig Scott, recommends the following best practices:
  • check the existence of `CMAKE_CONFIGURATION_TYPES` and only adding or pruning configurations if it's preset, *AFTER* your call to `project()` 
  • do not set `CMAKE_CONFIGURATION_TYPES` and use the `STRINGS` property on `CMAKE_BUILD_TYPE` for single config generators
  • forcibly set the `CMAKE_BUILD_TYPE` cache variable if it is unset, otherwise check the passed `CMAKE_BUILD_TYPE` against allowable configs and throw an error if it differs
  • set `CMAKE_<LANG>_FLAGS_<CONFIG>` and `CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>` variables as cache variables for newly defined configuration
The problem I encounter is that, if I set `-DCMAKE_BUILD_TYPE:STRING=<MY_CONFIG>` to CMake on the command line, then CMake seems to initialize all of the `<MY_CONFIG>` cache variables to the empty string *BEFORE* I can set them. All other custom configurations get set to my specified default cache variable value.

What is the best practice for setting the new configuration's default flags?

I am aware of `CMAKE_USER_MAKE_RULES_OVERRIDE` and all of the `CMAKE_<LANG|TARGET_TYPE>_FLAGS_<CONFIG>_INIT` variable, but as far as I can tell, there is no access to knowing what compiler is being used, since the compilers have yet to be probed. Is it possible to use the `CMAKE_..._INIT` variables to set per-compiler flags some how?

Any answers or pointers here would be most appreciated.

FYI, Before being enlightened to some of the dangers or non-standard ways of what I was doing, I would typically set `CMAKE_CONFIGURATION_TYPES` *before* my call to `project()`. Something like this:

```cmake
set ( CMAKE_CONFIGURATION_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "CodeCoverage" )
set ( CMAKE_BUILD_TYPE "Release"
  CACHE STRING "Select which configuration to build." )
set_property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CMAKE_CONFIGURATION_TYPES} )
```

I would then call `project()` so that the compilers would be probed and I could make specific flag choices based on the compiler being used, and set those as Cache variables. Something like this:

```cmake
if ("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU" )
  set(gfortran_compiler true)
  set ( CMAKE_C_FLAGS_CODECOVERAGE "-fprofile-arcs -ftest-coverage -O0"
    CACHE STRING "Code coverage C compiler flags")
  set ( CMAKE_Fortran_FLAGS_CODECOVERAGE "-fprofile-arcs -ftest-coverage -O0"
    CACHE STRING "Code coverage Fortran compiler flags")
```

This seemed to work fine and specifying, e.g., `-DCMAKE_BUILD_TYPE:STRING=CodeCoverage`, the `CMAKE_<LANG>_FLAGS_CODECOVERAGE` cache variable would be set to the appropriate value.

Thanks for taking the time to read my question.

-Zaak

--

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake
Reply | Threaded
Open this post in threaded view
|

Re: Correct way to specify multiple build configurations for single config generators?

Craig Scott-3


On Mon, Dec 10, 2018 at 1:37 PM Zaak Beekman <[hidden email]> wrote:
I have been reading the *excellent* book "Professional CMake". The author, Craig Scott, recommends the following best practices:
  • check the existence of `CMAKE_CONFIGURATION_TYPES` and only adding or pruning configurations if it's preset, *AFTER* your call to `project()` 
  • do not set `CMAKE_CONFIGURATION_TYPES` and use the `STRINGS` property on `CMAKE_BUILD_TYPE` for single config generators
  • forcibly set the `CMAKE_BUILD_TYPE` cache variable if it is unset, otherwise check the passed `CMAKE_BUILD_TYPE` against allowable configs and throw an error if it differs
  • set `CMAKE_<LANG>_FLAGS_<CONFIG>` and `CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>` variables as cache variables for newly defined configuration
The problem I encounter is that, if I set `-DCMAKE_BUILD_TYPE:STRING=<MY_CONFIG>` to CMake on the command line, then CMake seems to initialize all of the `<MY_CONFIG>` cache variables to the empty string *BEFORE* I can set them. All other custom configurations get set to my specified default cache variable value.

What is the best practice for setting the new configuration's default flags?

This is only going to be an issue for single-configuration generators. For multi-config generators, you don't set CMAKE_BUILD_TYPE and the flags you set as cache variables after the first project() call should be honoured. For single-config generators, you have a few choices:
  • Run CMake with CMAKE_BUILD_TYPE set to some other config first, then change it to the one you want after the first run. If the custom config is one that is only occasionally used and typically just one you switch to temporarily from time-to-time, then this may be okay for your situation. It's also easy enough to handle in a CI build, albeit slightly less efficient since you have to run CMake twice. It's a bit annoying, but it is at least easy.
  • Rather than setting the custom config's flags in the project, do it in a toolchain file instead (with the various ..._<CONFIG>_INIT variables). The flags are going to be toolchain-specific anyway, so you don't lose generality. You also gain the advantage that you can take that same toolchain file and apply it to other projects, so you get better reusability. This would be what I would use personally, but I'm comfortable with toolchain files. The main downside to this approach is that those new to CMake can feel a bit intimidated by toolchain files or see them as an unnecessary complication for a build that should just pick up the default compilers and work out of the box. It's a tradeoff, so I erred on the side of simplicity in the book and didn't make toolchains a recommendation for defining custom configs. If you and your users are happy with toolchain files though, I'd go this route.
  • You could set your custom config's cache variables (again I'd recommend setting the ..._<CONFIG>_INIT variables) before the first project() command, but you won't have the compiler ID or any other compiler information at that point. You also won't have any of the default configs' flags available to you, so you can't make your custom config an extension of an existing one (e.g. you can't start with flags from, say, RelWithDebInfo and add a few more to enable profiling like in the book's example). If your project only needs to consider one compiler and the flags are simple and well-defined, this is going to be the easiest for your developers, since it will work out-of-the-box with no extra effort on their part.
  • Instead of setting cache variables, you could append to regular non-cache ...<CONFIG> variables after the project() command. This has the advantage of being simple, but your config-specific flags won't show in the cache and developers can't override them without editing the project.
So as you can see from the above, there's no one perfect solution. The best choice really depends on your project and your users.

 

I am aware of `CMAKE_USER_MAKE_RULES_OVERRIDE` and all of the `CMAKE_<LANG|TARGET_TYPE>_FLAGS_<CONFIG>_INIT` variable, but as far as I can tell, there is no access to knowing what compiler is being used, since the compilers have yet to be probed. Is it possible to use the `CMAKE_..._INIT` variables to set per-compiler flags some how?

Use a toolchain file where you are in control of which compiler is selected. Use different toolchain files for different compilers.

 
Any answers or pointers here would be most appreciated.

FYI, Before being enlightened to some of the dangers or non-standard ways of what I was doing, I would typically set `CMAKE_CONFIGURATION_TYPES` *before* my call to `project()`. Something like this:

```cmake
set ( CMAKE_CONFIGURATION_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "CodeCoverage" )
set ( CMAKE_BUILD_TYPE "Release"
  CACHE STRING "Select which configuration to build." )
set_property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CMAKE_CONFIGURATION_TYPES} )
```

I would then call `project()` so that the compilers would be probed and I could make specific flag choices based on the compiler being used, and set those as Cache variables. Something like this:

```cmake
if ("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU" )
  set(gfortran_compiler true)
  set ( CMAKE_C_FLAGS_CODECOVERAGE "-fprofile-arcs -ftest-coverage -O0"
    CACHE STRING "Code coverage C compiler flags")
  set ( CMAKE_Fortran_FLAGS_CODECOVERAGE "-fprofile-arcs -ftest-coverage -O0"
    CACHE STRING "Code coverage Fortran compiler flags")
```

This seemed to work fine and specifying, e.g., `-DCMAKE_BUILD_TYPE:STRING=CodeCoverage`, the `CMAKE_<LANG>_FLAGS_CODECOVERAGE` cache variable would be set to the appropriate value.

This didn't work for me, it showed the same problem you described in your original issue. Perhaps you forgot to clear out a previous build first? It would only work after an initial run of CMake where you didn't set CMAKE_BUILD_TYPE or you set it to something else.


--
Craig Scott
Melbourne, Australia


--

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake
Reply | Threaded
Open this post in threaded view
|

Re: Correct way to specify multiple build configurations for single config generators?

Zaak Beekman

Hi Craig, et al,

Craig, once again, thank you for writing such a great reference and learning book! I have a few responses inline below.

On Mon, Dec 10, 2018 at 6:34 AM Craig Scott <[hidden email]> wrote:
This is only going to be an issue for single-configuration generators. For multi-config generators, you don't set CMAKE_BUILD_TYPE and the flags you set as cache variables after the first project() call should be honoured. For single-config generators, you have a few choices:
  • Run CMake with CMAKE_BUILD_TYPE set to some other config first, then change it to the one you want after the first run. If the custom config is one that is only occasionally used and typically just one you switch to temporarily from time-to-time, then this may be okay for your situation. It's also easy enough to handle in a CI build, albeit slightly less efficient since you have to run CMake twice. It's a bit annoying, but it is at least easy.
Come to think of it, this might be why my example below was working. During the CI process for that project we test the different configurations, so if the config is in question is *not* set first, then it's cache variables have a chance to initialize themselves before the build is switched to that config. 
  • Rather than setting the custom config's flags in the project, do it in a toolchain file instead (with the various ..._<CONFIG>_INIT variables). The flags are going to be toolchain-specific anyway, so you don't lose generality. You also gain the advantage that you can take that same toolchain file and apply it to other projects, so you get better reusability. This would be what I would use personally, but I'm comfortable with toolchain files. The main downside to this approach is that those new to CMake can feel a bit intimidated by toolchain files or see them as an unnecessary complication for a build that should just pick up the default compilers and work out of the box. It's a tradeoff, so I erred on the side of simplicity in the book and didn't make toolchains a recommendation for defining custom configs. If you and your users are happy with toolchain files though, I'd go this route.
Hhhmmm. Looks like I'm off to read about toolchain files. Fingers crossed its covered in your book!
  • You could set your custom config's cache variables (again I'd recommend setting the ..._<CONFIG>_INIT variables) before the first project() command, but you won't have the compiler ID or any other compiler information at that point. You also won't have any of the default configs' flags available to you, so you can't make your custom config an extension of an existing one (e.g. you can't start with flags from, say, RelWithDebInfo and add a few more to enable profiling like in the book's example). If your project only needs to consider one compiler and the flags are simple and well-defined, this is going to be the easiest for your developers, since it will work out-of-the-box with no extra effort on their part.
  • Instead of setting cache variables, you could append to regular non-cache ...<CONFIG> variables after the project() command. This has the advantage of being simple, but your config-specific flags won't show in the cache and developers can't override them without editing the project.
I find cache variables to be a large enough source of confusion as it is. In my experience users rarely use ccmake or cmake-gui. They tend to give up if they can't pass a few flags on the command line and achieve the desired results. And if they do, and see one value of the variable, yet the project seems to use a different value caused by shadowing the cache variable with a regular variable, this leads to extreme confusion.

I am aware of `CMAKE_USER_MAKE_RULES_OVERRIDE` and all of the `CMAKE_<LANG|TARGET_TYPE>_FLAGS_<CONFIG>_INIT` variable, but as far as I can tell, there is no access to knowing what compiler is being used, since the compilers have yet to be probed. Is it possible to use the `CMAKE_..._INIT` variables to set per-compiler flags some how?

Use a toolchain file where you are in control of which compiler is selected. Use different toolchain files for different compilers.

I actually just seem to have had some success through the combination of `CMAKE_..._INIT` variables passed in `CMAKE_USER_MAKE_RULES_OVERRIDE` when used in combination with setting the corresponding CACHE variables in the CMakeLists.txt. Now, this may not be a robust solution because it relies on some interesting behavior of the `if()` statement used from the `CMAKE_USER_MAKE_RULES_OVERRIDE` file. In the file specified by `CMAKE_USER_MAKE_RULES_OVERRIDE`  includes `if()` statements of the form `if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")` then the following code block is never actually executed. However, if one uses `if(CMAKE_C_COMPILER_ID STREQUAL "GNU")` it seems that the evaluation of the `if()` statement is deferred until a point in which the value of `CMAKE_C_COMPILER` has already been set.

It sounds like ToolChain files are probably a better solution here, however, It's nice that this quick and dirty trick seems to work (at least during my testing with 3.10 on Linux....). Does anyone have any insight into if this will be a robust and reliable approach?

This didn't work for me, it showed the same problem you described in your original issue. Perhaps you forgot to clear out a previous build first? It would only work after an initial run of CMake where you didn't set CMAKE_BUILD_TYPE or you set it to something else.

Yes,  That is probably true. This config is normally just used under CI testing in which we loop through the build configs, starting with standard pre-defined ones. So the cache is probably set by a previous call with CMAKE_BUILD_TYPE set to Debug or something.

Thanks for the swift response and the great book!

-Zaak

--

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake