22
votes

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.

4

4 Answers

13
votes

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.

3
votes

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 )
2
votes

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.

0
votes

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
    ...
)