How does add_subdirectory(... SYSTEM) work?

Hi all, fairly new to cmake.

I have a library that I want to use in my project but want its files to be treated as system header files when included in my own code.

Here’s my directory structure:

top
 src1
 src2
 src3
 include
 libs
  blah
    some_lib

In directory libs I have a CMakeLists.txt that has

add_subdirectory(blah/some_lib SYSTEM)

some_lib itself uses its own cmake setup to build. I expected all the include directories from some_lib to be using -isystem but they still use -I. I even tried adding

add_subdirectory(libs SYSTEM)

to my top level CMakeLists.txt but still no go.

Is this the right way to do what I want to do? Looking at the cmake documentation it’s not clear if the SYSTEM property will propagate to all subdirectories.

https://cmake.org/cmake/help/latest/command/add_subdirectory.html?highlight=add_subdirectory

Thanks,
Nick

I don’t see the docs mentioning that it is recursive, so I’d assume that it is not.

But in that case I’d be forced to edit the third-party library’s CMakeList.txt files, which is rather undesirable.

Nick

Yeah. Maybe there’s another way around this though. @craig.scott ?

I was trying to follow discussions like this https://gitlab.kitware.com/cmake/cmake/-/issues/18040#note_1197146 but they did not make it clearer what to do.

If I recall correctly, when you set the SYSTEM directory property to true, it propagates into any subdirectories that directory creates too. In other words, it should be acting recursively. It wouldn’t really make sense for it to work any other way, as already highlighted in comments above.

The behavior probably relates to details of the project not provided. You mentioned that the subdirectory uses its own cmake setup, but what does that mean? It is most likely related to that, but without any details about precisely how that is done and how you’re adding things from there into the main build, it’s hard to offer any further insight.

Could be. Is there a way to debug how the -I/-isystem paths are added to consumers?

I tried --debug-output/–log-level/–trace-source but it did not seem to have any info about that.

Depends what you mean by “how the -I/-isystem paths are added to consumers”. If you just want to see the compiler command lines so you can tell what flags are used, the method for that depends on what CMake generator you’re using. If you’re using Ninja, add --verbose to the ninja command line. If using Makefiles, add VERBOSE=1 to the make command line (I think this works, it’s been so long since I had to do that with Makefiles). If you’re using something else, you can probably find the details in the IDE settings somewhere.

I can see that the command lines don’t have the -isystem that I expected.

What I meant is to find where those command lines are created. Perhaps checking the SYSTEM property at that point can shed some light and I can somehow trace it back.

Can you show the contents of the file at libs/blah/some_lib/CMakeLists.txt? The details of that will probably shed some light on what’s really going on.

It’s

or-tools/CMakeLists.txt at stable · google/or-tools · GitHub

but when compilation lines for the consumers are created I see

-I/home/nick/code/build/_deps/zlib-src

instead of the expected

-isystem/home/nick/code/build/_deps/zlib-src

I’ll be honest, that’s a lot more detail than I have time to go through. I suggest you reduce your real world case down to a minimal project which demonstrates the problem. That exercise very often uncovers the underlying cause. If you can still reproduce the problem with a very minimal example, that will be more likely to be investigated further.

I can definitely understand :slight_smile: I have a decent workaround. As I get more comfortable with cmake, if I find the time, I’ll try to distill it and report it here.

Cheers!

If I remember right, it may be caused by an open cmake issue/23393

from installed stagedir/lib/cmake/greeter/greeterTargets.cmake

# Create imported target greeter::greeter
add_library(greeter::greeter STATIC IMPORTED)

set_target_properties(greeter::greeter PROPERTIES
  INTERFACE_COMPILE_FEATURES "cxx_std_20;cxx_std_20"
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include/greeter"
  INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:>"
  INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "include"  # <<<<<<<<<<<<
)

created from CMakeLists.txt

# target_include_directories with the SYSTEM modifier will request the compiler to omit warnings
# from the provided paths, if the compiler supports that
target_include_directories(
  ${PROJECT_NAME} SYSTEM PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
                                $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

But a local build works right:

bash-5.2$ ninja -nv GreeterStandalone 
[1/2] /usr/local/bin/ccache /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ \
 -I/Users/clausklein/Workspace/cpp/ModernCmakeStarter/build/standalone/_deps/greeter-build/PackageProjectInclude \
 -isystem /Users/clausklein/Workspace/cpp/ModernCmakeStarter/include -isystem /usr/local/include \
 -O3 -DNDEBUG -std=gnu++20 -isysroot \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk \
-mmacosx-version-min=13.6 -MD -MT CMakeFiles/GreeterStandalone.dir/Unity/unity_0_cxx.cxx.o \
-MF CMakeFiles/GreeterStandalone.dir/Unity/unity_0_cxx.cxx.o.d -o CMakeFiles/GreeterStandalone.dir/Unity/unity_0_cxx.cxx.o \
-c /Users/clausklein/Workspace/cpp/ModernCmakeStarter/build/standalone/CMakeFiles/GreeterStandalone.dir/Unity/unity_0_cxx.cxx

see standalone/CMakeLists.txt