Setting CMAKE_C_FLAGS breaks transitive include folders?

I’m trying to understand a failing behaviour where linking to an imported target isn’t properly using the include directory. The target has a proper INTERFACE_INCLUDE_DIRECTORIES set but it isn’t propagated to the compiler call if it’s set in the environment but CFLAGS are cleared before use. I can fix this by setting the flags in a more modern way (this is a very old inherited CMakeList) but it caught me by surprise and took a while to diagnose.

The files in the test:

  • test.c, contents: #include "subheader.h"
  • libdir/subheader.h that is empty
  • CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(test_flags)
set(CMAKE_C_FLAGS "") # Causes breaking behaviour
add_library(imported::target INTERFACE IMPORTED)
set_target_properties(imported::target PROPERTIES
      INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/libdir")
add_library(testinc test.c)
target_link_libraries(testinc PUBLIC imported::target)

Now, cmake <path> && make works fine. However, if I set the linking path export CFLAGS="-isystem <path>/libdir" (or -I equivalent) first then I get:

... fatal error: 'subheader.h' file not found

e.g. the transitive include path is not included in the target’s compile command. I encountered this with a find_package module that finds something in a conda environment, and conda explicitly sets up the CFLAGS to prefer it’s includes first over the system paths.

It looks like the list of includes to ignore is cached at some earlier point, and later when it comes to generate the compiler command it filters out the duplicates. However, this seems to have gotten out of sync.

Is this a bug, or have I missed something? Is this documented somewhere I should have seen?

This is likely related to CMake’s detection of built-in include paths to the compiler. Though I can’t say for sure what about it this is interfering with.

Cc: @brad.king

The project() command initializes support for C and CXX languages and performs introspection of the corresponding compilers. One of the things we detect is the implicit include path that the compiler uses. By putting -I or -isystem flags in CFLAGS, those end up in the initial CMAKE_C_FLAGS, which CMake assumes will always be used. Those flags are included in the initial detection of the implicit include path, so the paths specified in those flags are assumed to always be used by the compiler. CMake avoids explicitly adding flags for such paths to avoid breaking the compiler’s implicit include order. If you then remove flags that CMake assumed would always be present, then nothing tells the compiler to add those include directories anymore.