CMake on SMT32 | Episode 6: add C++ specific compiler options
Tags: embedded, cpp, microcontrollers, cmake
Published: 2020-05-04 21:28:52

In [episode 5, we started to use C++](https://dev.to/younup/cmake-on-stm32-episode-5-get-ready-for-c-1mh9) but we didn't (*) add C++ specific compiler options. In this episode we will see how to do this. Because guess what: C++ and C are different language and compiling C code with C++ specific options may _not_ be a good idea 😄

## Generator expression

(*) This isn't completely true: to fix a linker error I suggested to add `-fno-exceptions` and this is clearly a C++ specific option. I didn't show any CMake code but if you tried on your computer, I suppose you simply added it like this and compilation went fine:

```cmake
target_compile_options(${EXECUTABLE} PRIVATE
        # some options...

        -Wall
        -Wextra
        -pedantic

        -fno-exceptions

        $<$<CONFIG:Debug>:-Og>)
```

Let's try to add an highly recommended option for embedded C++, `-fno-rtti`, and recompile the project:

```cmake
target_compile_options(${EXECUTABLE} PRIVATE
        # some options...

        -Wall
        -Wextra
        -pedantic

        -fno-exceptions
        -fno-rtti

        $<$<CONFIG:Debug>:-Og>)
```

```bash
λ mingw32-make.exe all
[...]
[  3%] Building C object CMakeFiles/nucleo-f413zh.out.dir/BSP/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c.obj
cc1.exe: warning: command line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
[  7%] Building C object CMakeFiles/nucleo-f413zh.out.dir/BSP/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c.obj
cc1.exe: warning: command line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
[ 11%] Building C object CMakeFiles/nucleo-f413zh.out.dir/BSP/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c.obj
cc1.exe: warning: command line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
[ 15%] Building C object CMakeFiles/nucleo-f413zh.out.dir/BSP/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c.obj
cc1.exe: warning: command line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
[...]
```

OK 😦 Interesting 😐 What do we do?

The solution is in the title of this section (and shown in the previous code extract with `$<$<CONFIG:Debug>:-Og>`): a [generator expression](https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html) to add this option only when the language is C++. Simply replace `fno-rtti` with `$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>` in `target_compile_options()`. That's it! Problem solved! 😙

## GCC's option files

No doubt that you will add other options for C+++, such as `-Wsuggest-override`, `-Wold-style-cast` or `-Wuseless-cast`. You can use the generator expression each time an option causes warnings, but there is an easier solution: put all C++ options in a file and tell GCC to read this file with the [@ option](https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Overall-Options.html):

> `@file`
>
Read command-line options from `file`. The options read are inserted in place of  the original `@file` option. If `file` does not exist, or cannot be read, then the option will be treated literally, and not removed.
>
> Options in `file` are separated by whitespace. A whitespace character may be included in an option by surrounding the entire option in either single or double quotes. Any character (including a backslash) may be included by prefixing the character to be included with a backslash. The file may itself contain additional `@file` options; any such options will be processed recursively.

Create a simple text file called `gcc-options-cxx.txt`:

```text
-Wold-style-cast
-Wuseless-cast
-Wsuggest-override

-fno-exceptions
-fno-rtti
```

And change the compiler options:

```cmake
target_compile_options(${EXECUTABLE} PRIVATE
        # some options...

        -Wall
        -Wextra
        -pedantic

        $<$<COMPILE_LANGUAGE:CXX>:@${CMAKE_SOURCE_DIR}/gcc-options-cxx.txt>

        $<$<CONFIG:Debug>:-Og>)
```

This technique has advantages:

1. Generator expressions are quite heavy so having the option in a file improve readability of the CMake code.
2. You don't have to manually add the generator expression to each option.

But it has one drawback:

1. Source files are not recompiled when the file is changed 😕

I think we may find a CMake trick to trigger a full project rebuild. Since this file is almost never modified, I simply force a project rebuild if needed.

**Update from 2024 February  8th**. I don't known if this is a new feature in CMake, or if 4 years ago I wasn't aware of this possibility, but you can put all your C++ options into a single generator expression:

```cmake
        $<$<COMPILE_LANGUAGE:CXX>:
                -fno-rtti
                -Wold-style-cast
                -Wuseless-cast
                -Wsuggest-override
        >
```
 I strongly recommend this solution over a file with all the options. It may be fine when working alone on a project, but neither your coworkers not your CI server will see that the file has changed and that the whole project must be rebuilt.

## Avoid warnings caused by 3rd party headers

If you recompile the project with above options you will see that compilation of the C files (generated by STM32CubeMX) is OK. Nevertheless many warnings are raised on `application.cpp` because of useless casts. Example:

```bash
D:\cmake_stm32\BSP\Drivers\CMSIS\Include/core_cm4.h:1663:75: warning: useless cast to type 'uint32_t' {aka 'long unsigned int'} [-Wuseless-cast]
 1663 |   reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change               */
      |   
```

In episode 3, we learned how to [avoid warnings in 3rd party files](https://dev.to/younup/cmake-on-stm32-episode-3-handle-warnings-in-3rd-party-files-4gg4), but that was for **source** files.

Here, the warnings are caused by 3rd party **header** files in one of our source files.

The solution is to tell CMake that the directories that contains these files are [*system include directories*](https://cmake.org/cmake/help/v3.0/command/target_include_directories.html):

> If `SYSTEM` is specified, the compiler will be told the directories are meant as system include directories on some platforms (signalling this setting might achieve effects such as the compiler skipping warnings, or these fixed-install system files not being considered in dependency calculations - see compiler docs).

"See compiler docs" ? OK, let's have a look at GCC's documentation about [_Options for Directory Search_](https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html) and [how system directories are treated differently from normal directories](https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html). In the second page we can read:

> All warnings, other than those generated by `#warning` (see Diagnostics), are suppressed while GCC is processing a system header.

This is have the desired effect then!

Change your `CMakeLists.txt` so that you have to separate calls to `target_include_directories()`:

```cmake
target_include_directories(${EXECUTABLE} SYSTEM PRIVATE
        BSP/Inc
        BSP/Drivers/STM32F4xx_HAL_Driver/Inc
        BSP/Drivers/CMSIS/Device/ST/STM32F4xx/Include
        BSP/Drivers/CMSIS/Include)

target_include_directories(${EXECUTABLE} PRIVATE
        source/)
```

Warnings have disappeared :)

## Conclusion

In this episode, we have seen that generator expressions are again a good solution to conditionally add options. This is the modern C++ technique to add C++ specific options to the compiler. `COMPILE_LANGUAGE` in generator expression was introduced in CMake 3.3 and prior to this version, the now-deprecated technique was to modify the `CMAKE_CXX_FLAGS` property directly.

We have also seen that we can avoid warnings caused by 3rd party headers by considering them as system includes. We cannot modify them so we ask GCC to suppress their warnings.