CMake: target_include_directories() prints an error when I try to add the source directory itself, or one of its subdirectories
Asked Answered
S

2

73

I am writing a C++ library (header-only) and am using CMake to generate my (Visual Studio) project and solution files. I'm also writing a test suite, which is part of the same CMake project.

My problem occurs when I call target_include_directories() on the target that represents my header-only library, so that consumers of my library may find its header files. I get the following error message (even though generation is NOT aborted).

CMake Error in CMakeLists.txt:
  Target "Fonts" INTERFACE_INCLUDE_DIRECTORIES property contains path:

    "D:/Projects/GPC/fonts/include"

  which is prefixed in the source directory.

(D:/Projects/GPC/Fonts being the top-level directory of my library project. Btw the problem remains if I move my header files to the top directory.)

The offending line in my CMakeLists.txt is this (adapted for simplicity):

target_include_directories(Fonts INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")

I do not understand what I'm doing wrong. Without target_include_directories(), code of consumer projects simply can't include my header files (unless in installed form, but I haven't gotten to that yet, and in any case I want to be able to use my library from its build tree, without installation.)

I feel like I'm missing something basic here; yet I've searched for hours without finding a solution or explanation.

Subdeacon answered 4/9, 2014 at 23:2 Comment(2)
I think you'll need to give an SSCCE here. I tried a very basic test (gist.github.com/Fraser999/e877c25649e631f99ec8) which works for me with CMake v3.0Rocketry
@Fraser: thanks a lot for your wonderfully practical and concise snippet! However, I think ComicSansMS has found the root of the evil, so I probably won't be needing it.Subdeacon
P
140

The origin of the problem is not the target_include_directories command itself, but the attempt to install a target that has a public or interface include directory prefixed in the source path (i.e. the include directory is a subdirectory of your ${PROJECT_SOURCE_DIR}.)

While it is perfectly fine and desirable to use absolute paths when building the library from scratch, a third party library that pulls in a prebuilt version of that library will probably want to use a different include path. After all, you do not want all of your users to mirror the directory structure of your build machine, just to end up in the right include path.

CMake's packaging mechanism provides support for both of these use cases: You may pull in a library directly from the build tree (that is, check out the source, build it, and point find_package() to the directory), or from an install directory (run make INSTALL to copy built stuff to the install directory and point find_package() to that directory). The latter approach needs to be relocatable (that is, I build and install on my machine, send you the resulting directory and you will be able to use it on your machine from a different directory structure), while the former is not.

This is a very neat feature, but you have to account for it when setting up the include directories. Quoting the manual for target_include_directories:

Include directories usage requirements commonly differ between the build-tree and the install-tree. The BUILD_INTERFACE and INSTALL_INTERFACE generator expressions can be used to describe separate usage requirements based on the usage location. Relative paths are allowed within the INSTALL_INTERFACE expression and are interpreted relative to the installation prefix. For example:

target_include_directories(mylib PUBLIC  
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/mylib>  
    $<INSTALL_INTERFACE:include/mylib>  # <prefix>/include/mylib
)

The BUILD_INTERFACE and INSTALL_INTERFACE generator expressions do all the magic:

$<INSTALL_INTERFACE:...>

Content of ... when the property is exported using install(EXPORT), and empty otherwise.

$<BUILD_INTERFACE:...>

Content of ... when the property is exported using export(), or when the target is used by another target in the same buildsystem. Expands to the empty string otherwise.

Pahang answered 5/9, 2014 at 8:0 Comment(7)
Thanks ComicSansMS, you've hit the nail on the head, and clarified things for me. There's still a problem though: how can I use the values of these generator expressions when creating the config file ?Subdeacon
@Hans-PeterGygax Well, typically you want CMake to generate the config file for you. The whole packaging endeavor is, unfortunately, one of the most complex features of CMake, so you will have to spend some time digging through the docs and playing around. If it's of any help, I have a small library project on github that uses packaging with CMake 3.0 and is not too complex in its setup. Maybe seeing a complete working example will help clear things up. Check it out here.Pahang
Thanks a ton ComicSansMS (I really need a nickname of my own). Your example really is a treasure trove. What I'm trying to achieve is a reusable set of CMake files that can be configured via a few variables. Not entirely sure it's feasible, but your help sure goes a long way.Subdeacon
I've used your example to create a reusable library template. It probably needs more work, but if you have a mind, you can find it on Github: jpgygax68/CppTemplates.Subdeacon
It would be great if your answer explained what the heck "that has a public or interface include directory prefixed in the source path" meant. Is the public/interface directory part of the list of directories in the source path? Is the problem that the path is absolute rather than relative? (The error message already uses the same words, but to us tyros it's unintelligible in both places.)Heliotropism
@DanielGriscom If you use target_include_directories for setting your includes (as shown in the answer), you have to specify one of PUBLIC, PRIVATE or INTERFACE for the scoping of the include. Prefixed in the source path means just that, it's a subdirectory of your ${PROJECT_SOURCE_DIR}. Hopefully that clears up some of your confusion. This is a rather advanced feature and can require some fiddling around with before it clicks.Pahang
Great info; would you add it to your first paragraph? (I'd do it, but it's on the edge of my understanding and I'd hate to make a mistake.) Thanks.Heliotropism
F
0

Answer from @ComicSansMS uses generator expression. For users from other language, it's not that straightforward. Why not just change the properties that cmake error reports? Just use simple iteration, if/else statement.

The simplest solution:

set_target_properties(Fonts PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "")

To make it elegant, let's iterate each of the elements in existing INTERFACE_INCLUDE_DIRECTORIES, remove the ones that prefixed with ${CMAKE_CURRENT_SOURCE_DIR} or ${CMAKE_CURRENT_BINARY_DIR}:

macro(cleanup_interface_include_directories TARGET)
  get_target_property(original_interface_incs ${TARGET} INTERFACE_INCLUDE_DIRECTORIES)
  set(final_interface_incs "")
  foreach(inc ${original_interface_incs})
    if("${inc}" MATCHES "^(${CMAKE_CURRENT_SOURCE_DIR}|${CMAKE_CURRENT_BINARY_DIR})")
      continue()
    endif()
    list(APPEND final_interface_incs ${inc})
  endforeach()
  set_target_properties(${TARGET} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${final_interface_incs}")
  unset(final_interface_incs)
endmacro()

cleanup_interface_include_directories(Fonts)

Sometimes the target that cause cmake error, say Fonts, is part of a third party project, and I would like to keep the original CMakeLists.txt unchanged, build it from another project, say:

cmake_minimum_required(3.5)
project(x)

add_subdirectory("/path/to/Fonts" fonts.out)

set_target_properties(Fonts PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "")

# write installation of Fonts target here
install(...)
install(EXPORT ...)
Fernando answered 29/6 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.