I can't understand an error about INTERFACE_SOURCES and prefix

Hello,

I am doing the this tutorial to deploy a library :
https://decovar.dev/blog/2021/03/08/cmake-cpp-library/

I had to adapt it a little bit, but I have now a trouble when I do the Configs step. In this “chapter” there are several block of code. I did the first one, creating the file Config.cmake.in (note : the concerned CMake files are not at the top-level of the project). But when I call the first install() command in the second block code, I get many error saying :

CMake Error in src/CMakeLists.txt:
Target “MyLib” INTERFACE_SOURCES property contains path:

“absolute_path_to_an_include_file”

which is prefixed in the source directory.

Before presenting my CMakeLists.txt and talking about my project, it would be useful to explain me what happens in this very small piece of project, chich exhibits the same error message :
https://gitlab.kitware.com/cmake/cmake/-/issues/21879

Thanks.

CMake Error in src/CMakeLists.txt

Looking at the example repository that is linked in the article, I don’t see src/CMakeLists.txt there. Apparently, your project structure/sources are different, but you haven’t showed them.

From the error message it seems that your project is missing BUILD_INTERFACE/INSTALL_INTERFACE generators in one of the target_include_directories().

Thanks for your answer. It’s great that the author of this article takes place.

I’m off for now. It is the evening here.
The main difference with your project is that my library is splitted in several sublibraries (but there is only one binary at the end). So I have several subdirectories in my include directories. I had to use the loop, and to some adjustments (probably in a wrong way).

An other difference is that I have a test application in my project.

So I have something like that :

└── MyLibrary
    ├── CMakeLists.txt
    ├── include
    │   └── MyLibrary
    │       ├── SubLib1
    │       │   └── b.h
    │       ├── SubLib2
    │       │   └── c.h
    │       └── SubLib3
    │           └── d.h
    ├── src
    │   ├── CMakeLists.txt
    │   ├── SubLib1
    │   │   ├── a.h
    │   │   ├── a.cpp
    │   │   └── b.cpp
    │   └── SubLib3
    │       └── d.cpp
    └── test
        ├── CMakeLists.txt
        └── test.cpp

Buf for the CMakeLists.txt, you’ll have to wait some hours I came back to my office !

Here is the source of MyLibrary/src/CMakeLists.txt :

cmake_minimum_required(VERSION 3.17.3)

set(CMAKE_CXX_STANDARD 17)

set_property(GLOBAL PROPERTY USE_FOLDERS YES)

project(MyLibrary
        LANGUAGES CXX)

add_subdirectory(src)
add_subdirectory(test)

And here is the source of MyLibrary/src/CMakeLists.txt :

add_library(MyLibrary STATIC)

set(PUBLIC_SOURCE_LIST
    ${PROJECT_SOURCE_DIR}/include/MyLibrary/SubLib1/b.h
    ${PROJECT_SOURCE_DIR}/include/MyLibrary/SubLib2/c.h
    ${PROJECT_SOURCE_DIR}/include/MyLibrary/SubLib3/d.h
    )

set(PRIVATE_SOURCE_LIST
    SubLib1/a.h
    SubLib1/a.cpp
    SubLib3/d.cpp)

target_sources(MyLibrary
    PUBLIC
        ${PUBLIC_SOURCE_LIST}
    PRIVATE
        ${PRIVATE_SOURCE_LIST})

source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Sources FILES ${PRIVATE_SOURCE_LIST})
source_group(TREE ${PROJECT_SOURCE_DIR}/include/MyLibrary PREFIX Interface FILES ${PUBLIC_SOURCE_LIST})
        
# CMake will install by default the files C:/Program Files/MyLibrary,
# but we don't want pollute C:/Program Files, so we'll put the files
# somewhere in the project folders.
if(DEFINED CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    message(
        STATUS
        "CMAKE_INSTALL_PREFIX is not set\n"
        "Default value: ${CMAKE_INSTALL_PREFIX}\n"
        "Will set it to ${CMAKE_SOURCE_DIR}/install"
    )
    set(CMAKE_INSTALL_PREFIX
        "${CMAKE_SOURCE_DIR}/install"
        CACHE PATH "Where the library will be installed to" FORCE
    )
else()
    message(
        STATUS
        "CMAKE_INSTALL_PREFIX was already set\n"
        "Current value: ${CMAKE_INSTALL_PREFIX}"
    )
endif()

include(GNUInstallDirs)  # for CMAKE_INSTALL_INCLUDEDIR definition
set(CMAKE_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/include")

target_include_directories(MyLibrary
    PUBLIC
        $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR})

#=============================================================================

# To install the public header files, we cannot just set the PUBLIC_HEADER property on these
# files,and expect that the install() command will do correctly the job by using the parameter
# PUBLIC_HEADER DESTINATION: the file structure will not be respected.
# So instead we iterate through public headers and install them "manually".
# Note : May be obsolete since the introduction of File sets (see target_sources())
foreach(header ${PUBLIC_SOURCE_LIST})
    # message("------------------------------------------------------------------------------------")
    file(RELATIVE_PATH header_file_path "${PROJECT_SOURCE_DIR}" "${header}")
    get_filename_component(header_directory_path "${header_file_path}" DIRECTORY)
    file(REAL_PATH ./${header_directory_path} realPath)
    install(FILES ${header}
            DESTINATION "${CMAKE_INSTALL_PREFIX}/${header_directory_path}")
endforeach()

# Add a suffix for debug versions
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX "d")

message(STATUS "CMAKE_INSTALL_INCLUDEDIR : ${CMAKE_INSTALL_INCLUDEDIR}")

install(TARGETS ${PROJECT_NAME}
    EXPORT "${PROJECT_NAME}Targets"
    # These get default values from GNUInstallDirs, no need to set them
    # RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # bin
    # LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
    # ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
    # except for public headers, as we want them to be inside a library folder
    # PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} # include/MyLibrary
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} # include/MyLibrary
)

message(STATUS "CMAKE_CURRENT_LIST_DIR : ${CMAKE_CURRENT_LIST_DIR}")

When I do, from a build directory, the commands :

cmake --fresh -G "Visual Studio 15 2017" -A x64 ..
cmake --build . --target install

an install directory is created :

└── MyLibrary
    ├── CMakeLists.txt
    ├── build
    ├── include
    ├── install
    │   ├── include
    │   │   └── MyLibrary
    │   │       ├── SubLib1
    │   │       │   └── b.h
    │   │       ├── SubLib2
    │   │       │   └── c.h
    │   │       └── SubLib3
    │   │           └── d.h
    │   └── lib
    │       └── MyLibraryd.lib
    ├── src
    └── test

So evering is fine, until I add that at the end of src/CMakeLists.txt (the Config.cmake.in file from your tutorial has been created beside):

install(EXPORT "${PROJECT_NAME}Targets"
    FILE "${PROJECT_NAME}Targets.cmake"
    NAMESPACE ${namespace}::
    DESTINATION cmake)

I suspect this part:

target_sources(MyLibrary
    PUBLIC
        ${PUBLIC_SOURCE_LIST} # isn't really needed, as it's just the headers, and likely should not be PUBLIC here
    PRIVATE
        ${PRIVATE_SOURCE_LIST}
)

There is no need really to add headers to target sources, but if you’d like to do that still, try to put them under the PRIVATE scope instead of PUBLIC.

1 Like

You’re right ! :slight_smile:

Actually, they are needed so that they can appear in the Visual Studio GUI.
But I had to declare them private.

But I don’t understand what happens. Why declaring these headers as PUBLIC leads to errors ?

so that they can appear in the Visual Studio GUI

Yep, that’s the only reason I know about for adding headers to target_sources(). I mostly build stuff from bare CLI, so IDE-related “limitations” rarely concern me :slight_smile:

declaring these headers as PUBLIC leads to errors

My explanation might be not precise in wording, but in general declaring those as PUBLIC makes them part of the resulting package during installation, but that package needs to be “relocatable” (usable on other hosts/environments), so it cannot contain your absolute paths, as those will work only in your particular local environment.

I think I understand, thanks.

I go on with your great tutorial. Stay tuned ! :slight_smile:

Note that target_sources(PUBLIC FILE_SET headers TYPE HEADERS) can list them and supports installing them (as a fileset) as well. This also ensures they show up in IDEs. Basically, with a fileset, CMake understands that they are headers and can do more with them (like setting up target_include_directories automatically).

1 Like

Thank you. I’ll probably use that as an improvement once I make all my scripts work.