Override the default `-O3 -DNDEBUG` Release flags?

Hi,

What is the least-breaking way to override the -O3 -DNDEBUG flags automatically set by CMake for Release configurations, without touching any flags that the build system itself might have set in addition?

This question is being asked from a distributor’s standpoint, that is, not the project authors. Many Linux distributions have guidelines for the compilation flags used when building distributed software, and the default-to--O3 behavior sometimes contradicts these guidelines. For instance, in Arch Linux we have a policy of setting -DCMAKE_BUILD_TYPE=None due to this concern, but this policy has caused a number of issues due to missing -DNDEBUG that is occasionally relied on by various projects.

It is possible to override the flags variables themselves when invoking CMake:

$ cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=/usr \
  -DCMAKE_C_FLAGS_RELEASE="-O2 -DNDEBUG" \
  -DCMAKE_CXX_FLAGS_RELEASE="-O2 -DNDEBUG" \
  -S . -B build

However, this risks overwriting any modifications the project itself might perform on the CMAKE_(C|CXX)_FLAGS_RELEASE variables after they were initialized by CMake.


So, is there any way to tell CMake, in a generic fashion, to use something else instead of -O3 -DNDEBUG as the default value for Release compilation flags, without affecting any later modifications to these variables within the project buildsystem?

Don’t do that. None is not generally a valid build type, and it’s not a robust way to get the behavior you want. Instead, pick a configuration that is closest to what you want (likely Release) and override the default flags by either:

  • Specifying the relevant variables on the cmake command line (see below), or
  • Specifying the relevant variables in a toolchain file.

Since you want to completely replace the flags the CMake would normally set for you, the variables you want to be setting will be CMAKE_<LANG>_FLAGS and CMAKE_<LANG>_FLAGS_<CONFIG>. The former is used for all configurations, and the latter is appended after that for just the <CONFIG> configuration. The <LANG> part will be C, CXX, ASM, and so on depending on what languages you want to override the default flags for.

Command line example:

cmake -DCMAKE_CXX_FLAGS= "-DCMAKE_CXX_FLAGS_RELEASE=-O2 -DNDEBUG" ...

The above sets CMAKE_CXX_FLAGS to an empty string and CMAKE_CXX_FLAGS_RELEASE to -O2 -DNDEBUG. You would want to add more variables for other languages as well.

Sorry, hit send before responding to the second half of your question. If you want to only change the defaults and still let the project control these flag variables (which they shouldn’t be setting to begin with, but that’s a whole other topic), you can set the CMAKE_<LANG>_FLAGS_INIT and CMAKE_<LANG>_FLAGS_<CONFIG>_INIT variables instead. These are only used on the first run in a build directory, and they are used to initialise the non-INIT variables I mentioned in my previous reply. Again, you can set them either on the command line or in a toolchain file.

For regular users, they should be setting the ..._INIT variables in toolchain files. This still allows them to override the non-INIT variables if they want to. In your case, you want to explicitly control the exact flags used, so it sounds to me like you should be setting the non-INIT variables. If a project is trying to modify these variables (which used to be common many years ago in the early days of CMake, but is actively discouraged now), then it depends on how they try to do that. They would already be expecting the cache variables to be defined by the time they get a chance to read or modify them (well, technically that is after the first project() or enable_language() call that enables the language they want to control the flags for). Hopefully they are only setting non-cache variables and they are doing so by appending to the existing values, but you’re really at the mercy of whatever they decide to do.

Another discussion on this topic: https://gitlab.kitware.com/cmake/cmake/-/issues/21705

regards
A.

Don’t do that. None is not generally a valid build type, and it’s not a robust way to get the behavior you want.

I agree, but in the absence of any other way to control the CMake default flags this is what at least Arch Linux ended up writing into our guidelines.

In your case, you want to explicitly control the exact flags used, so it sounds to me like you should be setting the non-INIT variables. If a project is trying to modify these variables (which used to be common many years ago in the early days of CMake, but is actively discouraged now), then it depends on how they try to do that. They would already be expecting the cache variables to be defined by the time they get a chance to read or modify them (well, technically that is after the first project() or enable_language() call that enables the language they want to control the flags for). Hopefully they are only setting non-cache variables and they are doing so by appending to the existing values, but you’re really at the mercy of whatever they decide to do.

Well, all of this is true. As you correctly note, many free software projects have broken or just sloppily written build systems, and it would be clearly infeasible for us (or any other distribution) to patch or thoroughly review all of them.

This is exactly why my intent/request here is precisely to behave exactly as vanilla CMake would behave, except that -O3 should not be part of the default Release flags.

If you want to only change the defaults and still let the project control these flag variables (which they shouldn’t be setting to begin with, but that’s a whole other topic), you can set the CMAKE_<LANG>_FLAGS_INIT and CMAKE_<LANG>_FLAGS_<CONFIG>_INIT variables instead. These are only used on the first run in a build directory, and they are used to initialise the non-INIT variables I mentioned in my previous reply. Again, you can set them either on the command line or in a toolchain file.

Unfortunately, this was the first thing I tried. Setting any of the CMAKE_<LANG>_FLAGS_<CONFIG>_INIT variables on the command line results in CMake default flags being appended to the specified flags. Observe:

$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
project(hello)

set(CMAKE_CXX_STANDARD 17)

add_executable(hello_cpp main.cpp)
add_executable(hello_c main.c)
$ env CFLAGS="-O2 -g" CXXFLAGS="-O2 -g" \
  cmake \
   -DCMAKE_BUILD_TYPE=Release \
   -DCMAKE_C_FLAGS_RELEASE_INIT="-DNDEBUG" \
   -DCMAKE_CXX_FLAGS_RELEASE_INIT="-DNDEBUG" \
   -S . -B cmake-build-release
-- The C compiler identification is GNU 14.1.1
-- The CXX compiler identification is GNU 14.1.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/intelfx/devel/landfill/hello/cmake-build-release
$ grep -E '(C|CXX)_FLAGS_RELEASE:STRING' cmake-build-release/CMakeCache.txt
CMAKE_CXX_FLAGS_RELEASE:STRING=-DNDEBUG -O3 -DNDEBUG
CMAKE_C_FLAGS_RELEASE:STRING=-DNDEBUG -O3 -DNDEBUG

So, the unwanted -O3 flag still makes it to the resulting CMake cache.

Any other ideas?

What I’ve got in my CMake files:

set(CMAKE_CXX_FLAGS_DEBUG "" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE "" CACHE STRING "" FORCE)

notice the FORCE option.

Then I set my compiler options.

This is missing the point. I’m not speaking as a project author here, but as a distribution maintainer. This means that I’m not writing my own CMakeLists.txt from scratch, but working with an existing build system.

Understand. So I don’t know how to enforce that. In my case, I propose templated CMakeLists, that are enforcing a set of rules (such as these) but I don’t feel it can be an option for you.

Sorry

Yes, precisely. For a distribution, it is generally not an option to patch (let alone rewrite according to specific strict rules) the build systems of each distributed program — it simply does not scale.