Calling target_link_libraries with an interface library doesn't add include compiler flags on Windows

Consider two cmake projects

bar/
  CMakeLists.txt
  bar-config.cmake.in
  src/bar/bar.hpp
foo/
  CMakeLists.txt
  src/foo.cpp

source code (click the arrow to expand):

bar/CMakeLists.txt:

cmake_minimum_required(VERSION 3.18)

project(bar LANGUAGES CXX)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

#
# Define the library
#

add_library(bar INTERFACE)
add_library(bar::bar ALIAS bar)

# define the INTERFACE_INCLUDE_DIRECTORIES property
target_include_directories(bar INTERFACE
    $<BUILD_INTERFACE:${bar_SOURCE_DIR}/src>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

#
# Install the library
#

# define the export targets to install
install(TARGETS bar
        EXPORT bar-targets
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})

# generate the package config file
configure_package_config_file(
    bar-config.cmake.in bar-config.cmake
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bar)

# allow exporting from build tree
export(EXPORT bar-targets
       FILE ${CMAKE_CURRENT_BINARY_DIR}/bar-targets.cmake
       NAMESPACE bar::)

# install the exported targets
install(EXPORT bar-targets
        FILE bar-targets.cmake
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bar
        NAMESPACE bar::)

# install the config package config
install(FILES
        ${CMAKE_CURRENT_BINARY_DIR}/bar-config.cmake
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bar)

# install the header files
install(DIRECTORY src/
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
        FILES_MATCHING PATTERN "*.hpp")

export(PACKAGE bar)

bar/bar-config.cmake.in

@PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/bar-targets.cmake)
check_required_components(bar)

bar/src/bar/bar.hpp

#pragma once

#include <iostream>
namespace bar { void baz() { std::cout << "baz" << std::endl; } }

foo/CMakeLists.txt

cmake_minimum_required(VERSION 3.18)
project(foo)

find_package(bar REQUIRED)
add_executable(foo src/foo.cpp)
target_link_libraries(foo INTERFACE bar::bar)

foo/src/foo.cpp

#include <bar/bar.hpp>
int main() { bar::baz(); }

bar is a header-only library and foo is an application that uses the bar library. When I build the bar library I follow these steps:

$ cd bar
$ mkdir build && cd build
$ cmake ..
$ sudo cmake --install .

When I build the foo application, I follow similar steps

$ cd foo
$ mkdir build && cd build
$ cmake ..
$ cmake --build .

On linux, this compiles and works as expected. On Windows however - when building with gcc 11.2 and Ninja cmake generator, I get the error

[1/2] Building CXX object CMakeFiles/foo.dir/src/foo.cpp.obj
FAILED: CMakeFiles/foo.dir/src/foo.cpp.obj
C:\msys64\mingw64\bin\c++.exe    -MD -MT CMakeFiles/foo.dir/src/foo.cpp.obj -MF CMakeFiles\foo.dir\src\foo.cpp.obj.d -o CMakeFiles/foo.dir/src/foo.cpp.obj -c <path/to/project>/foo/src/foo.cpp
<path/to/project>/foo/src/foo.cpp:1:10: fatal error: bar/bar.hpp: No such file or directory
    1 | #include <bar/bar.hpp>
      |          ^~~~~~~~~~~~~
compilation terminated.
ninja: build stopped: subcommand failed.

You can see that a compiler flag is missing! In particular: -I<path/to/bar-include-directory>. I tried compiling with msvc and Visual Studio 16 2019 generator, and I get a similar error message. I’m able to fix this issue by adding the following to foo/CMakeLists.txt:

get_target_property(bar_includes bar::bar INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(foo PRIVATE ${bar_includes})

But shouldn’t this be done automatically by

target_link_libraries(foo INTERFACE bar::bar)

Is this a bug with CMake on Windows, or is there something I’m missing?

This adds bar::bar to foo's INTERFACE. This is not used for foo itself, but is for anything using foo. You probably want PUBLIC (though PRIVATE works if foo does not publicly use bar::bar's headers).

1 Like

I see …

Let’s say I want to add an interface dependency to the bar library - From what I understand, I add the following lines to bar/CMakeLists.txt:

find_package(baz REQUIRED)
target_link_libraries(bar INTERFACE baz::baz)

However, this fails when I try to generate the foo build files using target_link_libraries(foo PUBLIC bar::bar) with the message

  Target "foo" links to target "baz::baz" but the target was not found.
  Perhaps a find_package() call is missing for an IMPORTED target, or an
  ALIAS target is missing?

This is fixed if I add find_package(baz REQUIRED) to foo/CMakeLists.txt. Is it possible to do this without requiring the foo project to find the baz package?

I figured it out, I just need to add find_package(baz REQUIRED) to bar/bar-config.cmake.in