24

I would like to change the default values for CMAKE_CXX_FLAGS_RELEASE or CMAKE_CXX_FLAGS_DEBUG in CMake. Basically, I have some project defaults that differ just slightly from CMake's (for release, for instance), and I shouldn't have to ask myself "Oh, does their -O3 or our -O2 take precedence when added with add_compile_options."

Now, I know how to set these values, but I don't know how to make them user editable in the two usual ways: by using -DCMAKE_CXX_FLAGS_DEBUG=yourflags on the command line or by configuring it with ccmake or CMakeSetup.

The problem being that CMAKE sets and caches its own defaults for these, and if you try to overwrite the variables without using FORCE, the "defaults" are never changed. If I use FORCE in my set command: set(CMAKE_CXX_FLAGS_DEBUG blah CACHE STRING "" FORCE), it will overwrite it every time the script is run, eliminating the possibility for the user to change it if he wishes.

I managed to hack it to work with CCMAKE by doing the following, but this still doesn't work with cmake -DCMAKE_CXX_FLAGS_DEBUG as it overwrites the user change AFTER he's done it:

set(DEFAULTS_SET FALSE CACHE BOOL "")
set(CMAKE_CXX_FLAGS_DEBUG "-this -that" CACHE STRING "" FORCE)
set(DEFAULTS_SET TRUE CACHE BOOL "" FORCE)

Obviously, this is a nasty hack and doesn't completely work(in the case of cmake -Dwhatever=thisorthat). I could add other build types as well, but I don't really see why that should be necessary just to change a few simple things.

Edit March 1st, 2015:

I've created a solution that works, though I still am not super thrilled about what I have to do. I'd seen other comments that solve the problem of setting CMAKE_CXX_FLAGS_DEBUG and friends without having them get clobbered, but this originally didn't work for me because I was trying to also select them based on the compiler that was in use. The compiler isn't determined, however, until it has already filled the variables for me. The trick I used is as follows. You must set the flags variables to something 'special' before the project command.

set(CMAKE_CXX_FLAGS_DEBUG "_UNSET" CACHE STRING "")
project(your_project C CXX)

if(${CMAKE_CXX_FLAGS_DEBUG} STREQUAL "_UNSET")
    # Do some compiler switching here and then set your flags with FORCE.
    set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0" CACHE STRING "" FORCE)
endif()

This now allows me to choose defaults that are completely override-able via command line with -D or in cmake-gui.

2 Answers 2

27
+50

I just wanted to add the four possibilities I see:

  1. Having your own toolchain files containing the presets for each compiler you support like:

    GNUToolchain.cmake

     set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0" CACHE STRING "")
    

    And then use it with

     cmake -DCMAKE_TOOLCHAIN_FILE:string=GNUToolchain.cmake ...
    
  2. You can try to determine the compiler by checking CMAKE_GENERATOR (which is valid before the project() command):

    CMakeLists.txt

     if("${CMAKE_GENERATOR}" MATCHES "Makefiles" OR 
        ("${CMAKE_GENERATOR}" MATCHES "Ninja" AND NOT WIN32))
         set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0" CACHE STRING "")
     endif()
    
     project(your_project C CXX)
    
  3. You can use CMAKE_USER_MAKE_RULES_OVERRIDE to give a script with your own ..._INIT values:

    It is loaded after CMake’s builtin compiler and platform information modules have been loaded but before the information is used. The file may set platform information variables to override CMake’s defaults.

    MyInitFlags.cmake

     # Overwrite the init values choosen by CMake
     if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
         set(CMAKE_CXX_FLAGS_DEBUG_INIT "-ggdb3 -O0")
     endif()
    

    CMakeLists.txt

     set(CMAKE_USER_MAKE_RULES_OVERRIDE "MyInitFlags.cmake")
    
     project(your_project C CXX)
    
  4. You can simplify your solution from March 1st by checking against the ..._INIT variants of the compiler flag variables:

    CMakeLists.txt

     project(your_project C CXX)
    
     if (DEFINED CMAKE_CXX_FLAGS_DEBUG_INIT AND  
         "${CMAKE_CXX_FLAGS_DEBUG_INIT}" STREQUAL "${CMAKE_CXX_FLAGS_DEBUG}")
         # Overwrite the init values choosen by CMake
         if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
             set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0" CACHE STRING "" FORCE)
         endif()
     endif()
    

Comments:

I prefer and use the toolchain variant. But I admit it has the disadvantage of having to give the toolchain file manually (if you are not calling cmake via a script/batch file).

References:

2
  • You can concat new rules to previous rules, ex: set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall")
    – Sandburg
    Commented Feb 13, 2019 at 10:39
  • 1
    Option 3 seems like the "most correct" way to me. Thanks! Commented Jun 29, 2020 at 13:21
6

Florian's answer using toolchain files is a good one for earlier versions of CMake. But CMake 3.19 added a feature called presets that helps manage common sets of cache variables for your project. Basically, you create at least one of two files, CMakePresets.json and CMakeUserPresets.json (usually added to .gitignore or similar), that contain specifications of how to configure the project.

For example, you might write:

{
  "version": 1,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 19,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "default",
      "displayName": "Default",
      "description": "Build using Ninja and a GCC-like compiler",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_CXX_FLAGS_DEBUG": "-ggdb3 -O0"
      }
    },
    {
      "name": "default-vcpkg",
      "displayName": "Default (vcpkg)",
      "description": "Default build with vcpkg (from VCPKG_ROOT)",
      "inherits": "default",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
      }
    }
  ]
}

Then, from the source directory, your CMake command line would just become:

$ cmake --preset=default

This approach has a few advantages:

  1. It makes the command line a lot simpler
  2. It's compatible with other toolchain files (like vcpkg's in the second preset)
  3. It can override flags that are usually unconditionally added to the *_INIT flags.
  4. You don't have to write awkward logic in your CMakeLists.txt.
  5. Presets are opt-in for the user, which is important if you're distributing a library.

Expanding on points 4 and 5: it is a bad idea to add flags unless they absolutely must be there to compile correctly and there isn't a built-in feature for reaching those flags (eg. CMAKE_CXX_STANDARD). If someone tries to compile your library with a different compiler (or even a different version of the same compiler) they could run into issues if, for example, you add a warning flag that's too new or not supported. You can work around this with generator expressions and/or complex logic (like the _UNDEF trick above), but it's generally just easier and more convenient to use a toolchain or these new presets.

For instance, to correctly add -Wsuggest-override, you would need to write:

target_compile_options(lib PRIVATE $<$<AND:$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,5.1>,$<COMPILE_LANG_AND_ID:CXX,GNU>>:-Wsuggest-override>)

# ... or ...

# Note: only correct if using "PRIVATE". Must use a genex for INTERFACE/PUBLIC because the whole genex gets exported, whereas this flag will get exported verbatim.
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 5.1)
  target_compile_options(lib PRIVATE -Wsuggest-override)
endif ()

Or you could just put the flag in a toolchain/preset where you already know what compiler you're using.

Not the answer you're looking for? Browse other questions tagged or ask your own question.