# \[discussion\] \`COMPILE\_OPTIONS\` de-duplication "direction" (keep first vs keep last)

**URL:** https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264
**Category:** Development
**Created:** [August 14, 2022, 10:23pm UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264 "2022-08-14T22:23:30Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![david-fong](https://discourse.cmake.org/user_avatar/discourse.cmake.org/david-fong/32/2388_2.png) [@david-fong](https://discourse.cmake.org/u/david-fong)
#### Post date: [August 14, 2022, 10:23pm UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/1 "2022-08-14T22:23:30Z")

</div>

In [the docs for `prop_tgt/COMPILE_OPTIONS`](https://cmake.org/cmake/help/latest/prop_tgt/COMPILE_OPTIONS.html#option-de-duplication) concerning de-duplication, it doesn’t specify which duplicate is retained (first or last occurence).

> The final set of options used for a target is constructed by accumulating options from the current target and the usage requirements of its dependencies. The set of options is de-duplicated to avoid repetition.

- It seems that the first occurence is kept
- I think this can be an important thing to state (because,)
- I want to discuss one way that this could result in potentially unexpected/undesirable behaviour.

Note: What I will describe has never been a problem for me, or even relevent to me so far, and if this has never been brought up before to the CMake team, it probably means it’s not a problem for anybody, and we can all spend our time on more pressing things. Since this has never been a problem for me, the example I will later give is a toy example. It should not be taken seriously as if it were something that someone might actually do.

## Current CMake Behaviour

I tried the CMake de-duplication out, and it seems the _first occcurence_ is the one that is kept (I made a target and gave it the compile options `-funroll-loops -fno-unroll-loops -funroll-loops`, configured, and inspected the compile\_commands.json file, which had `-funroll-loops -fno-unroll-loops`). Relevant source code seems to be in [`Source/cmGeneratorTarget.cxx`](https://gitlab.kitware.com/cmake/cmake/-/blob/master/Source/cmGeneratorTarget.cxx#L3907), but I didn’t spend the time to read and understand it.

## “Problem” Background Info

In gcc (and other compilers that try to have some compatibility with it (clang, icc (I think? I’ve never used icc))), many compiler flags have a parallel flag that does the “opposite thing” ([docs](https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html)):

> Many options have long names starting with ‘-f’ or with ‘-W’—for example, -fmove-loop-invariants, -Wformat and so on. Most of these have both positive and negative forms; the negative form of -ffoo is -fno-foo. This manual documents only one of these two forms, whichever one is not the default.

and when multiple positive and negative forms are given in the same list of compiler options, the last one controlling that behaviour is the one that is used:

Ex. [warning options](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html):

> Some options, such as -Wall and -Wextra, turn on other options, such as -Wunused, which may turn on further options, such as -Wunused-value. The combined effect of positive and negative forms is that more specific options have priority over less specific ones, independently of their position in the command-line. **For options of the same specificity, the last one takes effect**. Options enabled or disabled via pragmas (see [Diagnostic Pragmas](https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html#Diagnostic-Pragmas)) take effect as if they appeared at the end of the command-line.

The gcc documentation is a _little_ lacking- as shown above, the warning options docs explicitly state how resolution is done, but the [optimization options docs](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html) and others do not. But you can use [`-frecord-gcc-switches`](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html) and inspect the output to see what “final” list of options was used to compile, which will show that it uses the last one.

## “Problem” Description

The pre-deduplication list of compile options seen by CMake orders compile options from least to most specific context. From the docs for [`CMAKE_<LANG>_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS.html):

> - `CMAKE_CXX_FLAGS`: Initialized by the `CXXFLAGS` environment variable.
> 
> […]  
> The flags in this variable will be passed to the compiler before those in the per-configuration `CMAKE_<LANG>_FLAGS_<CONFIG>` variant, and before flags added by the `add_compile_options()` or `target_compile_options()` commands.

To me, it seems reasonable to believe that generally, the most specific context “knows best” about what compile options to use- or at least that that’s much more reasonable than believing the opposite.

_Toy_ example: imagine a user about to compile a project has `CXXFLAGS` with `-funroll-loops`, and the project sets `CMAKE_CXX_FLAGS_DEBUG` with `-fno-unroll-loops`, and then for a specific target, there’s a `target_compile_options` with `$<$<CONFIG:Debug>:-funroll-loops>`.

It might be surprising that (for the debug config in this example,) the target which requested to have `-funroll-loops` now has `-fno-unroll-loops`.

(Reminder- this is a toy example, and loop unrolling is probably not the best option to use. Perhaps imagine other scenarios with options such as `-fzero-call-used-regs`, `-fconserve-stack`, `-fno-inline`). Important note: I don’t know if such setups are even legal/safe (ie. could they result in ODR violations or other badness for inline functions defined in headers which are included in multiple targets with different compiler options?).

## Questions for the CMake Team

- What was the rationale for making the first occurence of a compile option be the one to be retained?
  - Was what I’ve talked about taken into consideration?
  - Are there any conflicts? Ie. Are there ways that retaining the _first_ occurence of an option could be desirable?

- Would you consider adding to all instances of this documentation describing target compile option de-duplication that the _first_ occurence is kept?

## Possible Questions for General Discussion

- Can you think of something better than my toy example? Ie. A setup that has a good rationale that someone who doesn’t know about CMake’s compile option de-duplication behaviour would be surprised about the outcome of.

- (copied from above): Are there any conflicts? Ie. Are there ways that retaining the _first_ occurence of an option could be desirable?

- If this is seen as something that should be addressed, how can it best be addressed? Possible options for discussion:

---

<div class="post-metadata">

### Author: ![david-fong](https://discourse.cmake.org/user_avatar/discourse.cmake.org/david-fong/32/2388_2.png) [@david-fong](https://discourse.cmake.org/u/david-fong)
#### Post date: [August 21, 2022, 7:26pm UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/2 "2022-08-21T19:26:58Z")

</div>

Note: I looked at the table of contents for Craig’s “Professional CMake” book and saw that there is a chapter on option de-duplication, but have not yet purchased a copy of the book to read, so there might be relevant info / answers to my questions there.

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [August 21, 2022, 10:26pm UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/3 "2022-08-21T22:26:53Z")

</div>

I don’t know the history of the de-duplication functionality, but I can provide an example where keeping the first occurrence is important: header search paths. If you retained the last occurrence for header search paths, it would change the search behavior, potentially finding a different header than if no de-duplication occurred. Repeated include paths are pretty common, so I wouldn’t be surprised if this was the first area the de-duplication was used.

For other compiler and linker options, relying on the last one taking precedence can be ill-advised. I think some toolchains will issue a warning about later options overriding earlier options. I’d normally recommend you avoid setting up such a scenario by removing unwanted options from the command line. That’s also much clearer for anyone trying to debug the command line later for some reason.

The current documentation that talks about de-duplication does lack specific details about how that de-duplication is done. If someone was willing to trace through the code and confirm the behavior (noting whether there are any special cases), a merge request updating that documentation would be welcome.

---

<div class="post-metadata">

### Author: ![david-fong](https://discourse.cmake.org/user_avatar/discourse.cmake.org/david-fong/32/2388_2.png) [@david-fong](https://discourse.cmake.org/u/david-fong)
#### Post date: [November 17, 2022, 2:07am UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/4 "2022-11-17T02:07:41Z")

</div>

Note: the CL compiler also favours right-most options when there are conflicting options specificed.

> **[Order of CL Options (C++) - Visual Studio](https://learn.microsoft.com/en-us/cpp/build/reference/order-of-cl-options?view=msvc-170)**
>
> Learn more about: Order of CL Options

> If CL encounters conflicting options, it uses the rightmost option.

Of course this doesn’t change the fact that- as Craig pointed out- include directories make sense to dedup by keeping the first one. I’m just updating with information that I didn’t previously know.

---

<div class="post-metadata">

### Author: ![david-fong](https://discourse.cmake.org/user_avatar/discourse.cmake.org/david-fong/32/2388_2.png) [@david-fong](https://discourse.cmake.org/u/david-fong)
#### Post date: [November 26, 2022, 3:41am UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/5 "2022-11-26T03:41:42Z")

</div>

I don’t know about the history of the deduplication functionality either, but if the rationale for keep-first behaviour is specifically for include directories, perhaps it no longer holds value, since it’s now [recommended to use `target_include_directories`](https://cmake.org/cmake/help/latest/command/target_compile_options.html#see-also) for specifying include directories, in which case I think it would make sense to only do keep-left/first de-duplication on the final list of include directories for a target/file.

---

<div class="post-metadata">

### Author: ![david-fong](https://discourse.cmake.org/user_avatar/discourse.cmake.org/david-fong/32/2388_2.png) [@david-fong](https://discourse.cmake.org/u/david-fong)
#### Post date: [March 1, 2023, 6:41am UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/6 "2023-03-01T06:41:18Z")

</div>

Related question on Stack Overflow: [How can I overwrite interface compile options coming from an imported library in CMake?](https://stackoverflow.com/q/75599736/11107541)

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [March 1, 2023, 3:36pm UTC](https://discourse.cmake.org/t/discussion-compile-options-de-duplication-direction-keep-first-vs-keep-last/6264/7 "2023-03-01T15:36:04Z")

</div>

There’s no mechanism provided to ignore any given option specified as a usage requirement. To be able to skip, something like `$<$<NOT:$<TARGET_PROPERTY:skip_this_flag>>:-fsome-flag>` can be used so that consuming targets can set their own `skip_this_flag` property to ignore it.
