28

To suppress compiler warnings that originate from libraries I use in my application, I manually include their directories with target_include_directories(myapp SYSTEM ...) as system libraries before adding them with target_link_libraries like so:

add_executable(myapp myapp.cpp)
target_include_directories(myapp SYSTEM
  PRIVATE "extern/lib/include"
)
target_link_libraries(myapp lib::lib)

However, that kind of feels hacky and will also break if the developers of lib decide to change the include path. This wouldn't be a problem if using only target_link_library but then, of course, they are included via -I and again I would get compiler warnings coming from this include.

Is there any more elegant and fail-safe way of doing this? It would be great if target_link_libraries had a SYSTEM option to tell cmake to include it as a system library.

sebastian
  • 2,386
  • 26
  • 22
  • I don't remember exactly since it is a few years ago, but I probably didn't see 51816807 at first, otherwise I would obviously have not created this question. I guess while I was in the process of solving my own question, I stumbled across 51816807 at some point when it was too late, so I also replied there. I didn't want to hijack that question, didn't have any bad intentions. – sebastian Nov 04 '22 at 08:50
  • I'm pretty sure that I did my research before asking the question, I always do. Maybe I just queried the wrong search terms, I don't remember, it's been 4 years. Maybe the now-obvious duplicate (or this question) had different titles or text body than today, so the questions weren't clear duplicates back then. – sebastian Nov 07 '22 at 13:53

6 Answers6

16

I defined a function to handle this for me:

function(target_link_libraries_system target)
  set(libs ${ARGN})
  foreach(lib ${libs})
    get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
    target_include_directories(${target} SYSTEM PRIVATE ${lib_include_dirs})
    target_link_libraries(${target} ${lib})
  endforeach(lib)
endfunction(target_link_libraries_system)

I can now call target_link_libraries_system(myapp lib::lib) and the include directories are read from the target's properties.

This can be extended to optionally specify the PUBLIC|PRIVATE|INTERFACE scope:

function(target_link_libraries_system target)
  set(options PRIVATE PUBLIC INTERFACE)
  cmake_parse_arguments(TLLS "${options}" "" "" ${ARGN})
  foreach(op ${options})
    if(TLLS_${op})
      set(scope ${op})
    endif()
  endforeach(op)
  set(libs ${TLLS_UNPARSED_ARGUMENTS})

  foreach(lib ${libs})
    get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
    if(lib_include_dirs)
      if(scope)
        target_include_directories(${target} SYSTEM ${scope} ${lib_include_dirs})
      else()
        target_include_directories(${target} SYSTEM PRIVATE ${lib_include_dirs})
      endif()
    else()
      message("Warning: ${lib} doesn't set INTERFACE_INCLUDE_DIRECTORIES. No include_directories set.")
    endif()
    if(scope)
      target_link_libraries(${target} ${scope} ${lib})
    else()
      target_link_libraries(${target} ${lib})
    endif()
  endforeach()
endfunction(target_link_libraries_system)

This extended version will also print a warning if a library didn't set its INTERFACE_INCLUDE_DIRECTORIES property.

sebastian
  • 2,386
  • 26
  • 22
  • That's gorgeous! Really helped me. It is worth noticing that this function will also work applied to libraries imported via Hunter. – saleph Jun 15 '19 at 22:43
  • It seems that this is not working for projects generated for Visual Studio? – kiki Jul 31 '19 at 09:11
  • 1
    Doesn't seem to work for transitive dependencies. I can fix it by adding dependencies on my transitive dependencies, but that's not as cool. – user2394284 Feb 11 '20 at 15:37
  • @kiki "SYSTEM" is currently broken in the cmake Visual Studio generator ( https://gitlab.kitware.com/cmake/cmake/-/issues/18272# ) – Étienne Jun 18 '21 at 14:27
4

I modified Sebastian's solution to include the scope.

function(target_link_libraries_system target scope)
  set(libs ${ARGN})
  foreach(lib ${libs})
    get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES)
    target_include_directories(${target} SYSTEM ${scope} ${lib_include_dirs})
    target_link_libraries(${target} ${scope} ${lib})
  endforeach(lib)
endfunction(target_link_libraries_system)
Aidan Gallagher
  • 173
  • 1
  • 7
  • 2
    Since I now was in the situation where I needed this, I implemented a version with optional keyword handling. Thanks for the inspiration ;) – sebastian Jul 04 '20 at 20:39
4

To start with, you haven't specified whether your target that you're linking to is IMPORTED or not. I'm assuming that it is IMPORTED because include directories for IMPORTED targets are SYSTEM by default. Note: This behaviour, can be disabled. Pre-CMake v3.25, one would use one of NO_SYSTEM_FROM_IMPORTED or IMPORTED_NO_SYSTEM. For CMake 3.25 and later, one would modify the SYSTEM property of the target.

That typically just leaves the cases of targets added via add_subdirectory, which includes those added by FetchContent in its non-find_package mode. In that case, you can see the FetchContent Q&A here, and the add_subdirectory Q&A here. In summary, for pre-CMake v3.25, use a workaround in which you copy/move the INTERFACE_INCLUDE_DIRECTORIES target property to the INTERFACE_SYSTEM_INCLUDE_DIRECTORIES target property, and for CMake v3.25 or later, you can modify the SYSTEM target property, or the SYSTEM directory property, or use the SYSTEM argument of add_subdirectory/FetchContent_Declare.

starball
  • 20,030
  • 7
  • 43
  • 238
3

This was asked in the CMake discourse and @ben.boeckel (CMake developer) answered:

IMPORTED targets should already have their include directories treated as SYSTEM though. There is the NO_SYSTEM_FROM_IMPORTED target property to disable it.

phcerdan
  • 730
  • 7
  • 16
  • True, but unfortunately apparently this is not working with the visual studio generator: https://gitlab.kitware.com/cmake/cmake/-/issues/20422 and https://gitlab.kitware.com/cmake/cmake/-/issues/18272 I think this is why this question was asked on stackoverflow. – Étienne Jun 18 '21 at 14:13
0

Possible gotcha: If you are like me, and start every project by enabling all warnings in the toplevel directory…

# Warning level
add_compile_options(
    -Wall -Wextra -Wpedantic

    -Werror=switch
    -Werror=return-type
    -Werror=uninitialized
    -Werror=format-security
    -Werror=reorder
    -Werror=delete-non-virtual-dtor
    $<$<CONFIG:Debug>:-Werror>
)

… then, this obviously gets inherited by all subprojects (like git submodules, or what have you).

If this is the case, the solution is simple – be specific: Do it in a subdirectory, and/or use target_compile_options, for good measure:

target_compile_options(myTarget PRIVATE
    ...
)
user2394284
  • 5,520
  • 4
  • 32
  • 38
0

I defined a function which will declare a library as a system library, and handle all the include tagging itself.

function(declare_system_library target)
  message(STATUS "Declaring system library ${target}")
  get_target_property(target_aliased_name ${target} ALIASED_TARGET)
  if (target_aliased_name)
    set(target ${target_aliased_name})
  endif()
  set_target_properties(${target} PROPERTIES
    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>)
endfunction()

All this function does is add the target's include directories to it's system include directories.

For example, calling declare_system_library(fmt::fmt) will mean the include directories for the fmt::fmt library are now system libraries, allowing me to freely link against this library without worrying about declaring it system every time.

declare_system_library(fmt::fmt)

add_executable(my_exe main.cpp)
target_link_libraries(my_exe PRIVATE fmt::fmt)

// NOT NEEDED
// get_target_property(fmt_include_dirs fmt::fmt INTERFACE_INCLUDE_DIRECTORIES)
// target_include_directories(my_exe SYSTEM PRIVATE ${lib_include_dirs})
Shuba
  • 376
  • 2
  • 6
  • 14