Best way of adding compiler flags in toolchain

I’m writing a toolchain file to cross-compile and I need to set some compiler flags (e.g. -arch x86_64).
Now I’m wondering what the best way is to add those flags.

It works if I use add_compile_options and add_link_options. There’s also the options to set CMAKE_CXX_FLAGS and CMAKE_EXE_LINKER_FLAGS as cache variables - which I read in this old post.

However, I’ve also seen CMAKE_<LANG>_FLAGS_INIT, which on paper looks like exactly what I want. However, if I do that it seems that it’s completely ignored.

Is there another way, or do I need to do something specific to make the *_INIT options work correctly.

The CMAKE_<LANG>_FLAGS_INIT variables will only have an effect the first time CMake is run in a build directory. After that, the CMAKE_<LANG>_FLAGS variables will already be set and CMAKE_<LANG>_FLAGS_INIT gets ignored. I also mention the following in my Professional CMake book, which may also be relevant to you here:

Unfortunately, there are some inconsistencies in how CMake combines developer-specified ..._INIT options with the defaults it normally provides. In most cases, CMake will append further options to those specified by ...INIT variables, but with some platform/compiler combinations (particularly older or less frequently used ones), developer-specified ..._INIT values can be discarded. This stems from the history of these variables, which used to be for internal use only and always unilaterally set the ..._INIT values. From CMake 3.7, the ..._INIT variables were documented for general use and the behavior was switched to appending rather than replacing for the commonly used compilers. The behavior for very old or no longer actively maintained compilers was left unmodified.

All that said, the CMAKE_<LANG>_FLAGS_INIT variables probably still are the most appropriate way to do what you’re trying to achieve. I’d only look for a different method if you’re using a toolchain where CMake doesn’t handle those variables in the way that you need them to.

1 Like

Turns out it was something completely else. I was setting CMAKE_CXX_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, but I was not setting CMAKE_C_FLAGS_INIT. Since I did not specify languages in my project() call, CMake defaulted to both C and CXX, and it tried to compile and link a simple C program.

This didn’t work because none of the required flags (e.g. -arch ...) was getting set for the compilation, but it was getting set for the linker, which then got fed an object file with the wrong architecture.

I’ve now managed to get it to work, but I am still running into a strange issue. It seems that the flags I set in CMAKE_CXX_FLAGS_INIT are also getting fed during the link step, which leads to warnings, since I use the -nostdinc++ flag which is only relevant for compilation, but not for linking.

If I use add_compile_options, the flags are only added for compilation and not for linking, which is exactly what I want. Is there a way to avoid having CMake mis-use the flags for linking and still use the CMAKE_<LANG>_FLAGS_INIT?

Sometimes it’s less clear cut whether a particular flag is more appropriately set in a toolchain file or in the project. One way of looking at it is that the toolchain file should specify the things that must be set for a toolchain to be able to build anything at all for the target platform. Projects can then add further flags that tailor the compilation and linking of their targets. Ask yourself the question “Could I use this toolchain file with any other project building for the same target platform?”.

A flag like nostdinc++ falls into a bit of a grey area. To me, it probably belongs in the project, since different projects (even different targets within the same project) might or might not need this flag. But I’ve also seen cases where flags of this nature are set in toolchain files, which might be appropriate in tightly controlled company environments where the toolchain files might be tied to specific builds.

I mention the above since it sounds like the flag you’re having trouble with might be better specified in the project anyway, in which case add_compile_options() or target_compile_options() could give you the behavior you ultimately need. I think the variables like CMAKE_C_FLAGS may end up being passed to the linker as well, but I don’t recall the exact behavior and whether it is even consistent across toolchains.

Well, the point is that this is specifically made for cross-compiling using a specific set of libraries/headers that work for the target platform. It’s necessary to set nostdinc++ so that the compiler won’t try to include the standard library from its regular path (which, of course, won’t work since it doesn’t matches what’s on the target platform).

So this toolchain will never ever work without that flag. For me that feels like a solid reason why it should be in the toolchain file.

Which brings me back to my previous question: How can I set compiler flags in the toolchain file without them being fed during the linker step? I add the compiler flags using CMAKE_CXX_FLAGS_INIT, but that also appears during linking (which is unwanted). Why does it work when I use add_compile_options? From the documentation they should behave similar.

The linker is typically invoked via the compiler front end. That’s why CMAKE_C_FLAGS and CMAKE_CXX_FLAGS can end up being used on the linker command line as well. There was a discussion about this recently either here in this forum or in the gitlab issue tracker. You could go searching for that if you want a more detailed discussion.

I don’t know the reasoning behind why the CMAKE_<LANG>_FLAGS variables and the properties set by commands like add_compile_options() behave differently in this regard.