67

I have a C++ project which uses CMake as its build system. I'd like the following behavior:

If cmake is invoked as cmake .., then CMAKE_CXX_FLAGS is -O3 -Wall -Wextra

If cmake is invoked as cmake .. -DCMAKE_BUILD_TYPE=Debug, then CMAKE_CXX_FLAGS is -g -Wall -Wextra

I tried the following

message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

set(CMAKE_CXX_FLAGS "-O3 -Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")

But this has a big problem. First of all, if the second invocation is used, then both -O3 and -g flags are passed to the compiler. Besides, if I use the second invocation and the first thereafter, CMAKE_BUILD_TYPE stays Debug although not explicitly ordered so - so I get a Debug build although I want an optimized build.

Why? What can I do to get the desired behavior?

3 Answers 3

114

First off: recommended usage of CMake is to always specify CMAKE_BUILD_TYPE explicitly on the command line (if and only if using a single-configuration generator). Your use case deviates from this best practice, so treat this answer as "how you can do it," not necessarily as "how you should do it."

To address the first issue, you should be able to do this early in your CMakeList:

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

This will make sure that if you do not specify a build type at all, it will default to "Release" and thus CMAKE_CXX_FLAGS_RELEASE will be used.

The second one is harder to tackle. Variables passed from the command line (such as CMAKE_BUILD_TYPE=Debug) are cached by CMake and thus re-used in subsequent invocations (that is necessary, since CMake can re-trigger itself if you modify its inputs between builds).

The only solution is to make the user switch the build type explicitly again, using cmake .. -DCMAKE_BUILD_TYPE=Release.

Consider why this is necessary: as I said, CMake can re-trigger itself as part of a build if CMake's input (CMakeLists.txt files or their dependencies) has changed since last CMake ran. In such case, it will also be run without command-line arguments such as -DCMAKE_BUILD_TYPE=whatever, and will rely on the cache to supply the same value as last time. This scenario is indistinguishable from you manually running cmake .. without additional arguments.

I could provide a hacky solution to always reset CMAKE_BUILD_TYPE to Release if not specified explicitly on the command line. However, it would also mean that a buildsystem generated as Debug would get re-generated as Release if automatic re-generation happened. I am pretty sure that's not what you want.

6
  • This works until I make a debug build. After a single debug build, all the subsequent ones are debug builds, even if cmake .. is used
    – marmistrz
    Commented Dec 28, 2016 at 12:06
  • 1
    I don't remember where I've read it, but it's considered bad practice to set CMAKE_BUILD_TYPE from inside a CMakeLists.txt. Prefer to pass it from CMake command line.
    – roalz
    Commented Dec 28, 2016 at 12:08
  • @marmistrz I've added an update on why this happens, and how to "reset" it. I am trying to think of a way to achieve an implicit reset, but it's not easy. Commented Dec 28, 2016 at 12:08
  • @roalz The OP wants rather non-standard usage of CMake in the first place. I will however add a suitable disclaimer. Commented Dec 28, 2016 at 12:09
  • 1
    "recommended usage of CMake is to always specify CMAKE_BUILD_TYPE explicitly on the command line ..." - Not according to this Debian bug report: CMake sets the default optimization level to -O3.
    – jww
    Commented Sep 18, 2017 at 0:42
12

For CXX flags specific for Release target, you should set
CMAKE_CXX_FLAGS_RELEASE
instead of
CMAKE_CXX_FLAGS

In your case you can use:

set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

A more modern CMake approach (which I suggest, if you are using CMake version 2.8.12 or newer), is well described in this StackOverflow answer and involves the use of target_compile_options.

6
  • 1
    I would say "-g -O0". Commented Dec 28, 2016 at 12:06
  • But then the -O3 is not passed to the compiler when cmake ..
    – marmistrz
    Commented Dec 28, 2016 at 12:07
  • 1
    @n.m. gcc doesn't optimize at all by default
    – marmistrz
    Commented Dec 28, 2016 at 12:07
  • 1
    @marmistrz yes, but still it won't hurt. Commented Dec 28, 2016 at 12:13
  • @marmistrz: when no CMAKE_BUILD_TYPE is specified, CMAKE_CXX_FLAGS is used, as stated in the CMake wiki: cmake.org/Wiki/CMake_Useful_Variables
    – roalz
    Commented Dec 28, 2016 at 12:28
1

The default optimization level for various release modes is O3, which often isn't the best choice. Within CMakeLists.txt file, these can be modified to O2:

# Modify compile flags to change optimization level from O3 to O2
string(REGEX REPLACE "([\\/\\-]O)3" "\\12"
  CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REGEX REPLACE "([\\/\\-]O)3" "\\12"
  CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REGEX REPLACE "([\\/\\-]O)3" "\\12"
  CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}")
string(REGEX REPLACE "([\\/\\-]O)3" "\\12"
  CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")

These regular expressions will be modified (e.g.):

  • -O3 to -O2 usually for Linux-based compilers
  • /O3 to /O2 usually for Windows-based compilers

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