Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake error - Target foo INTERFACE_SOURCES property contains path which is prefixed in the source directory

Tags:

c++

cmake

I'm trying to create a C++ library that can be re-used via CMake. It's failing when I try to install the export files for the project. I don't understand why. Here's the error I get.

Target "Proj_LibA" INTERFACE_SOURCES property contains path:

  "C:/projects/cmake_temp/src/libA/include/liba.hpp"

which is prefixed in the source directory.

Reading the CMake documents and this other stackoverflow post imply that there's something wrong with how I setup source file paths and/or the include directory. Here's a SSCE that reproduces my issue.

Folder structure

cmake_temp/
          /build
          /install
          /src/
              /CMakeLists.txt
          /src/libA/
                   /include/liba.hpp
                   /CMakeLists.txt
                   /liba.cpp
                   /LibAConfig.cmake.in

/src/CMakeLists.txt

cmake_minimum_required (VERSION 3.15)
project("TestProj")

set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")

set(include_install_dir ${CMAKE_INSTALL_PREFIX}/include/)
set(export_cmake ${CMAKE_INSTALL_PREFIX}/cmake)
set(lib_install_dir ${CMAKE_INSTALL_PREFIX}/lib)
set(bin_install_dir ${CMAKE_INSTALL_PREFIX}/bin)

add_subdirectory(libA)
#add_subdirectory(exec)

liba/include/liba.hpp

#ifndef liba
#define liba

#include "LibA_export.hpp"

class PROJ_LIBA_EXPORT Foo
{
public:
    Foo(const int bias);
    int add(int a, int b);
private:
    int mBias;
};

#endif //liba

src/liba/liba.cpp

#include "liba.hpp"
Foo::Foo(const int bias) : mBias(bias) {}
int Foo::add(int a, int b) { return a + b + mBias; }

src/liba/CMakeLists.txt

# Setup alias to support add_subdirectory, find_package, and fetchcontent usage
add_library(Proj_LibA SHARED)
add_library(proj::liba ALIAS Proj_LibA)
set_target_properties(Proj_LibA PROPERTIES
    EXPORT_NAME LibA
    POSITION_INDEPENDENT_CODE TRUE)

target_sources(Proj_LibA
    PUBLIC
        include/liba.hpp
    PRIVATE
        liba.cpp)

target_include_directories(Proj_LibA
   PUBLIC
      $<INSTALL_INTERFACE:${include_install_dir}>
      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
   #PRIVATE
)

# Generate symbol export macros and add to source
include(GenerateExportHeader)
set(export_file "${CMAKE_CURRENT_BINARY_DIR}/LibA_export.hpp")
generate_export_header(Proj_LibA EXPORT_FILE_NAME ${export_file})
target_sources(Proj_LibA PUBLIC ${export_file})

# Install everything and mark it as part of the 'sdk' export package
install(TARGETS Proj_LibA
   EXPORT sdk
   ARCHIVE DESTINATION ${lib_install_dir}
   LIBRARY DESTINATION ${lib_install_dir}
   RUNTIME DESTINATION ${bin_install_dir}
)

# Install header files for package consumers
INSTALL(DIRECTORY include/ DESTINATION ${include_install_dir})

# Create the LibAConfig.cmake file for find_package
include(CMakePackageConfigHelpers)
configure_package_config_file(LibAConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/LibAConfig.cmake
  INSTALL_DESTINATION ${export_cmake}
  PATH_VARS include_install_dir)

# Create the LibAConfigVersion.cmake file for find_package
write_basic_package_version_file(
  ${CMAKE_CURRENT_BINARY_DIR}/LibAConfigVersion.cmake
  VERSION 1.2.3
  COMPATIBILITY SameMajorVersion )

# Install the LibAConfig*.cmake files
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/LibAConfig.cmake
              ${CMAKE_CURRENT_BINARY_DIR}/LibAConfigVersion.cmake
        DESTINATION ${export_cmake})

# Install the auto-generated export support/find_package scripts
install(EXPORT sdk
   DESTINATION ${export_cmake}
   NAMESPACE proj::)

# HELP: The above command triggers the following errors
#CMake Error in libA/CMakeLists.txt:
#  Target "Proj_LibA" INTERFACE_SOURCES property contains path:
#
#    "C:/projects/cmake_temp/src/libA/include/liba.hpp"
#
#  which is prefixed in the source directory.
#
#
#CMake Error in libA/CMakeLists.txt:
#  Target "Proj_LibA" INTERFACE_SOURCES property contains path:
#
#    "C:/projects/cmake_temp/build/libA/LibA_export.hpp"
#
#  which is prefixed in the build directory.

src/liba/LibAConfig.cmake.in

set(LibA_VERSION 1.2.3)

@PACKAGE_INIT@

set_and_check(LibA_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")

check_required_components(LibA)

Does anyone have suggestions on what I should look at to troubleshoot this error when trying to export my library?

like image 757
Tansir1 Avatar asked Jun 12 '20 16:06

Tansir1


1 Answers

I also hit this problem yesterday. Reading through docs and a blog post of "co-maintainer of CMake" helped to shed some light on this (tldr; see the Complications For Installing paragraph).

The issue here comes from from your liba.hpp being added as a PUBLIC target source, which defines an absolute path to your header file. This is valid in your build tree on your local machine, but likely to be different when it is installed, using find_package() or copying your library into another source tree. I think a lot of people, including myself, make assumptions that PUBLIC or INTERFACE is associated with a public header file, which is not the case. Quote from the blog post above:

[...] do not confuse the PRIVATE , PUBLIC and INTERFACE keywords with whether a header is part of the public API for the library or not, the keywords are specifically for controlling which target(s) the sources are added to

Just like in the SO link that you gave (same as in your code) the answer points to BUILD_INTERFACE and INSTALL_INTERFACE generator expressions to fix this problem for an include directory. One possible solution is to be more explicit and use same expressions in your target_sources()

target_sources(Proj_LibA
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/liba.hpp>
        $<INSTALL_INTERFACE:include/liba.hpp>
    PRIVATE
        liba.cpp)

This is arguably not a very pretty solution as this has to be done for each header file. Another option is to simply move your headers into the PRIVATE scope. Yet another one is to use PUBLIC_HEADER target property to define your headers and specify an install destination, I haven't actually tried this, but see this SO.

like image 138
alexm Avatar answered Nov 02 '22 22:11

alexm