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?