89

I know how to pass compiler options using the cmake command:

set(CMAKE_CXX_FLAGS "-Wall -Wno-dev -Wl,-rpath=/home/abcd/libs/")

Is there also a way to pass the options from the command line that will override the CMakeLists.txt options? Something like:

cmake -Wl,-rpath=/home/abcd/newlibs/ path/to/CMakeLists.txt

or

cmake -D CMAKE_CXX_FLAGS="-Wno-dev -Wl,-rpath=/home/abcd/libs/" path/to/CMakeLists.txt

My main problem is that I want to know how to append flags and how to override existing compiler flags from the command line.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
infoclogged
  • 3,641
  • 5
  • 32
  • 53
  • 7
    `-Wno-dev` is a **CMake option**, `-Wall` is a **compilation option**, `-Wl` begins **link option**. They are passed differently. And while *compilation option* and *linker option* has something common, *CMake option* is unrelated to them. – Tsyvarev May 31 '17 at 13:31

6 Answers6

81

Yes, you can append compiler and linker options. But there are two things you have to differentiate in CMake: the first call to generate the build environment and all consecutive calls for regenerating that build environment after changes to your CMakeLists.txt files or dependencies.

Here are some of the possibilities (excluding the more complex toolchain variants):

Append Compiler Flags

  1. The initial content from the cached CMAKE_CXX_FLAGS variable is a combination of CMAKE_CXX_FLAGS_INIT set by CMake itself during OS/toolchain detection and whatever is set in the CXXFLAGS environment variable. So you can initially call:

     cmake -E env CXXFLAGS="-Wall" cmake ..
    
  2. Later, CMake would expect that the user modifies the CMAKE_CXX_FLAGS cached variable directly to append things, e.g., by using an editor like ccmake commit with CMake.

  3. You can easily introduce your own build type like ALL_WARNINGS. The build type specific parts are appended:

      cmake -DCMAKE_CXX_FLAGS_ALL_WARNINGS:STRING="-Wall" -DCMAKE_BUILD_TYPE=ALL_WARNINGS ..
    

Append Linker Flags

The linker options are more or less equivalent to the compiler options. Just that CMake's variable names depend on the target type (EXE, SHARED or MODULE).

  1. The CMAKE_EXE_LINKER_FLAGS_INIT, CMAKE_SHARED_LINKER_FLAGS_INIT or CMAKE_MODULE_LINKER_FLAGS_INIT do combine with the evironment variable LDFLAGS to CMAKE_EXE_LINKER_FLAGS, CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS.

    So you can e.g call:

     cmake -E env LDFLAGS="-rpath=/home/abcd/libs/" cmake ..
    
  2. See above.

  3. Build type-specific parts are appended:

     cmake -DCMAKE_SHARED_LINKER_FLAGS_MY_RPATH:STRING="-rpath=/home/abcd/libs/" -DCMAKE_BUILD_TYPE=MY_RPATH ..
    

Alternatives

Just be aware that CMake does provide a special variable to set complier/linker flags in a platform independent way. So you don't need to know the specific compiler/linker option.

Here are some examples:

Unfortunately, there is none for the compiler's warning level (yet)

References

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Florian
  • 39,996
  • 9
  • 133
  • 149
  • 1
    unfortunately the approach with `cmake -E env CXXFLAGS="..." ..` doesn't work on Windows - the error "Access is denied" is shown even when running the tool with administrative permissions. – AntonK Nov 06 '21 at 22:38
  • When I try adding `-E env CXXFLAGS="-Wall"` to my cmake command, it says `CMake Warning: Ignoring extra path from command line: "CXXFLAGS=-Wall"`. – Aaron Franke Jul 19 '23 at 18:33
36

My answer aims to prove one thing:

Command line options like CMAKE_C_FLAGS and CMAKE_CXX_FLAGS always append and never overwrite.

Here it comes.

Prepare files under folder hello_world

hello.c

#include <stdio.h>


int main(int argc, char* argv[]) {
    printf("Hello World!\n");
#ifdef DEFINED_IN_CMAKELISTS
    printf("You are here because you defined DEFINED_IN_CMAKELISTS in CMakeLists and it is not overwritten.\n");
#else
    printf("You are here because CLI CMAKE_C_FLAGS overwrote DEFINED_IN_CMAKELISTS, or you have NOT defined DEFINED_IN_CMAKELISTS.\n");
#endif 
#ifdef DEFINED_IN_CLI
    printf("You are here because you defined DEFINED_IN_CLI when running cmake -DCMAKE_C_FLAGS.\n");
#else
    printf("You are here because you have NOT defined DEFINED_IN_CLI when running cmake -DCMAKE_C_FLAGS.\n");
#endif // #ifdef DEFINED_IN_CLI
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
project(Hello)

set(HELLO_SRCS Hello.c)

add_executable(Hello ${HELLO_SRCS})

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEFINED_IN_CMAKELISTS")

Generate CMake files

$ mkdir _build && cd _build && cmake ..

-- The C compiler identification is AppleClang 11.0.3.11030032
-- The CXX compiler identification is AppleClang 11.0.3.11030032
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/me/Desktop/_dev/playground/cmake/hello_world/_build

Make and run

$ make

Scanning dependencies of target Hello
[ 50%] Building C object CMakeFiles/Hello.dir/Hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
$ ./Hello

Hello World!
You are here because you defined DEFINED_IN_CMAKELISTS in CMakeLists and it is not overwritten.
You are here because you have NOT defined DEFINED_IN_CLI when running cmake -DCMAKE_C_FLAGS.

Define new compiler options from command line

$ cmake -DCMAKE_C_FLAGS="-DDEFINED_IN_CLI" ..

-- Configuring done
-- Generating done
-- Build files have been written to: /Users/me/Desktop/_dev/playground/cmake/hello_world/_build

Make and run

$ make

[ 50%] Building C object CMakeFiles/Hello.dir/Hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
$ ./Hello 

Hello World!
You are here because you defined DEFINED_IN_CMAKELISTS in CMakeLists and it is not overwritten.
You are here because you defined DEFINED_IN_CLI when running cmake -DCMAKE_C_FLAGS.

Conclusion

From the above test, you can see that even without hard-appending using something like

-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -DDEFINED_IN_CLI"

, CMake still appends the CLI options to what's already in CMakeLists.txt.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kakyo
  • 10,460
  • 14
  • 76
  • 140
  • 1
    The experiment is partially misleading! In my environment (Windows 10, CMake 3.21.4, VS2019) when looking into `CMakeCache.txt` the default flags are `CMAKE_C_FLAGS:STRING=/DWIN32 /D_WINDOWS /W3`, and when the command line option is specified these flags become `CMAKE_C_FLAGS:STRING=-DDEFINED_IN_CLI`, which is totally different story. – AntonK Nov 06 '21 at 12:06
  • 2
    Your conclusion is confusing: you say "even without hard-appending...", but then in the CMakeLists.txt you're using, it _literally has the line_ `set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEFINED_IN_CMAKELISTS")`. I have verified that if you change this line to `set(CMAKE_C_FLAGS "-DDEFINED_IN_CMAKELISTS")`, then `cmake -DCMAKE_C_FLAGS="-DDEFINED_IN_CLI" ..` no longer works as intended. So your conclusion should be: "If the project's CMakeLists.txt file is set up in this specific way, then `-DCMAKE_C_FLAGS=` will do the trick; but if the project's CMakeLists.txt is different, this might not work." – Quuxplusone Dec 24 '22 at 16:34
  • To expand on @AntonK's comment - as soon as you do `-DCMAKE_C_FLAGS` from the command line, you "lose" the default values set in `CMAKE_CXX_FLAGS_INIT`. So a command-line `-DCMAKE_C_FLAGS` does NOT always append. Rather, all that you've shown is that your custom `CMakeLists.txt` will always append the specific value you've set it up to, `-DDEFINED_IN_CMAKELISTS`. – Paul Molodowitch Apr 12 '23 at 12:00
16

Use:

cmake -D CMAKE_CXX_FLAGS="-Wno-dev -Wl,-rpath=/home/abcd/libs/" path/to/CMakeLists.txt

This should work. The problem is that if you find_package() some package that also changes the CMAKE_CXX_FLAGS, then it would not only partially work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tomaz Canabrava
  • 2,320
  • 15
  • 20
  • 3
    okay, I guessed that too for overriding. But how do you append an option to the existing option. Here "=" wont work. – infoclogged Jun 01 '17 at 10:17
  • sorry, I don't know how to append a variable via commandline. *MAYBE* CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} NewFlag" will work from commandline. – Tomaz Canabrava Jun 01 '17 at 10:34
  • That should not be expected to work as may shells will interpret ${SOMETOKEN} to be a shell expansion of a shell variable. Single quotes however might work, haven't tested. – Catskul Jun 20 '18 at 13:55
4

Perhaps this would work -

cmake -DCMAKE_CXX_FLAGS="$(CMAKE_CXX_FLAGS) -DYOUR_CUSTOM_DEFINE=1" <rest of original cmake cmdline>

like Tomaz mentioned above. -m

M Kelly
  • 51
  • 1
  • 2
  • 1
    Code only answers are discouraged. Please add some explanation as to how this solves the problem, or how this differs from the existing answers. [From Review](https://stackoverflow.com/review/late-answers/25360205) – Nick Feb 15 '20 at 01:47
  • The syntax is slightly different, using () instead of {} shown above. I tested to verify it works correctly. This method adds what is specified to the existing CXX_FLAGS and does not overwrite it. – M Kelly Feb 15 '20 at 03:08
0

Most of the answers here are valid, but I have also stumbled on how to pass CMAKE_CXX_FLAGS and add an include directory with a space in it (Windows).

Apparently, if you run that argument from the command line, you need to be extra careful with quotation (see also here).

cmake ... -DCMAKE_CXX_FLAGS="-fms-compatibility-version=19.00 --target=i686--windows -X -I """C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um""" "

So if the include path contains spaces, and that needs to be quoted, but you need also to quote CMAKE_CXX_FLAGS, which ends up with starting quotation with a single quote character ("), and whenever you need a quote, you place three quotation characters instead. (""")

That's a bit odd in overall, and it took a while to figure this out.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
0

I simply use the $ENV() operator to get the environment variable, for example in a CMakeLists.txt:

add_compile_options($ENV{MY_CXXFLAG})

The only problem is that $ENV() is only read on in the configuration stage, so CMake does not see the environment setting on the current build stage. But reconfiguring is triggered by changed CMake files, so I just use touch to simulate a change. Here is an example of a command line:

touch CMakeLists.txt && MY_CXXFLAG="-D DEBUG" cmake --build build --config Debug

or what other options do you use. With this simple example, there are still some quirks with the flag string of the environment variable, e.g., more than one option. But it shouldn't be a big problem with string handling in CMakeLists.txt to beautify this.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ingo
  • 588
  • 9
  • 21