IMPORTANT: CMAKE_BUILD_TYPE
only makes sense for single-target generators, like Makefiles. It is not used for multi-target generators as those simply generate a build system capable of building all build types (debug, release, etc).
CMAKE_BUILD_TYPE
is about,
- Optimization (level) [
-O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4
]
- Including 'debug info' in the executable [
-g, -gline-tables-only, -gmodules, -g
level, -gcoff, -gdwarf, -gdwarf-
version, -ggdb, -grecord-gcc-switches, -gno-record-gcc-switches, -gstabs, -gstabs+, -gstrict-dwarf, -gno-strict-dwarf, -gcolumn-info, -gno-column-info, -gvms, -gxcoff, -gxcoff+, -gz[=
type]
]
- Generating code for
assert()
or not [-DNDEBUG
]
- Including debug (output) code or not [custom]
Most such compiler options are compiler and/or platform specific. So, extended support for a build type needs updating every existing tool chain that you want to support.
The default build types that come with cmake more or less mean the following,
1. Release: high optimization level, no debug info, code or asserts.
2. Debug: No optimization, asserts enabled, [custom debug (output) code enabled],
debug info included in executable (so you can step through the code with a
debugger and have address to source-file:line-number translation).
3. RelWithDebInfo: optimized, *with* debug info, but no debug (output) code or asserts.
4. MinSizeRel: same as Release but optimizing for size rather than speed.
In terms of compiler flags that usually means (since these are supported in most cases on all platforms anyway):
1. Release: `-O3 -DNDEBUG`
2. Debug: `-O0 -g`
3. RelWithDebInfo: `-O2 -g -DNDEBUG`
4. MinSizeRel: `-Os -DNDEBUG`
Where defining NDEBUG
is added on platforms that support this (it disables assert()
). This is why you should make sure that none of your asserts have side effects, of course.
Extending the build type
Although adding stuff that needs different options for different tool chains is not something you really want to do in general (although, compiler options are basically compiler/language specific, so you could rather easily check the compiler ID if you wanted and then pick your flags depending on that).
It is rather easy to add support in the form of altering optimization flags or debug flags when you restrict yourself to [-g, -O0, -O2, -O3
and-Os
], removing a possible -DNDEBUG
flag and/or adding custom macros.
Suppose we have a macro DEBUG
that we want to define to include specific debug code (which could include writing debug output for example).
Then we have four optimization levels, debug info or not, assert code or not and debug code or not, for a total of 4 * 2 * 2 * 2 = 32 configurations (build types). But clearly not all configurations are very practical. It is better to look at what the use case is for a configuration.
Clearly we have the Release
build, which is bug-free code that is released at large; it is production code. You will not compile it very often and when you do it is more important that the resulting code is fast (or small?) than that it matters how long it takes to compile it. That leads to the two existing build types for production code:
1. Release
2. MinSizeRel
But then it turns out there is a bug after all in the production code that makes the application crash. You can't reproduce it and it only happens sometimes. You implemented a feedback mechanism for your users to send you the core dump, but the info just isn't enough. You want to get the stack trace in the hope it will tell you more. You ask certain users (or maybe yourself, using it on a daily basis as 'user') to download a special version that is usable as normal (it is fast enough, optimized) but it has debug information included, so takes a lot longer to download. Those users don't mind that: they want this crash to be fixed. This is supported with
3. RelWithDebInfo
Of course, you as the developer need a version that you can step through with a debugger. It doesn't have to be fast - you already know how to reproduce a bug that doesn't depend on optimization (it is a logic bug, a problem in your code - not a Heisenbug). For this you use,
4. Debug
But -- you also have beta testers (maybe you yourself on a daily basis using the program as a 'user'). In this case you want the code to be optimized, so it is fast enough - but you want also all asserts to be turned on; an assert might tell you where a problem is way better than a core dump that happens later. Or worse, it might just behave strangely and not crash at all. You need to be sure that none of your asserts fire, even in production code. That is what beta testers are for. Lets call this build type,
5. BetaTest [`-O3 -g`] - aka Release minus the `-DNDEBUG` but with `-g`.
Finally, there debug builds that are not to step through with a debugger; some bugs are simply not reproducable, nor does a stack trace help (because either it doesn't core dump, or the problem isn't causing an immediate crash). There are many, if not most, such bugs. And the only way to find those bugs (once they occur) is with extra debug code and/or loads of debug output written to a log file. You want this code to be compiled with -O2
at least, but you want asserts on too (why not) and you need the macro DEBUG
to be defined. We might as well include debug info too, because the size of the executable is of lesser concern here, of course. Lets call such a build
6. RelWithDebug [`-O2 -g -DDEBUG`] - aka RelWithDebInfo but `-DNDEBUG` removed and `-DDEBUG` added.
I suggest -O2
here because this is what you'd compile with most, as developer, because you yourself will always be such a user, because IF something unexpected happens you want to know what caused it (have those logs!) and you don't want to compile with the much slower -O3
all the time...
To support these two extra build types we need to be able to do two
things therefore: get the flags of an existing build type (change them) and use those flags for a new (custom) build type.
Here is how to do this
If you add the following four lines somewhere to the top of your project roots CMakeLists.txt
file then using -DCMAKE_BUILD_TYPE=BetaTest
(or RelWithDebug
), will use the flags as outlined above. Of course you might have to make more changes if anything else in your .cmake
files depends on the build type. Here is an example of what I am using personally: CW_OPTIONS.cmake (look, case insensive for betatest
and relwithdebug
plus the variables that are set as a result of what values those two have).
string(REGEX REPLACE "( -DNDEBUG$|-DNDEBUG )" "" CMAKE_CXX_FLAGS_BETATEST "${CMAKE_CXX_FLAGS_RELEASE}" )
string(REGEX REPLACE "( -DNDEBUG$|-DNDEBUG )" "" CMAKE_C_FLAGS_BETATEST "${CMAKE_C_FLAGS_RELEASE}" )
string(REGEX REPLACE "-DNDEBUG " "" CMAKE_CXX_FLAGS_RELWITHDEBUG "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DDEBUG" )
string(REGEX REPLACE "-DNDEBUG " "" CMAKE_C_FLAGS_RELWITHDEBUG "${CMAKE_C_FLAGS_RELWITHDEBINFO} -DDEBUG" )