3

instead of asking a question directly, I'll expose my use case and the way I tried (but failed) to solve it.

Say I have:

  • 3 shared libraries A, B and C
  • A require B and C
  • A comes with a set of headers

That's it, no extra information, it's provided by a vendor and not possibly subject to any change (modernization/cmake packages, etc). A should always be packaged with B and C. I should only need to link with A and cmake should transitively link with B and C.

Now, I'd like to make it more "modern cmake" friendly and by able to:

  1. First usecase: Create a repo containing these libs and calling add_subdirectory() from a parent project.
  2. First usecase: Create a package (say debian pkg . deb) containing the relevant AConfig.cmake AConfigVersion.cmake and ATargets.cmake. Then a simple system install of the pkg and a find_package() should to the trick.

What has been done:

I tried using INTERFACE IMPORTED library and INTERFACE.

Because I want to support packaging the libs INTERFACE IMPORTED can't be used (you can't install it as far as I know/tested).

INTERFACE is working fine for the first usecase, using add_subdirectory(), headers are found, everything links, but because the user may not have at this point, the shared lib in is path, he can't run the tests for instance.

Then comes the export part needed to make the shared libs available and to make find_package() work. I succeed to export/package the libs A B C, the headers for A and the files needed for find_package().

But when in a client library D, find_package(A REQUIRED) finds the lib (no messages such as "Could not find a package configuration file provided by "A" ") it doess NOT create any target I can link on. I took a look at the generated ATargets.cmake:

# generated stuff before

add_library(A::A INTERFACE IMPORTED) # This is cannot be used at all, does not create a target I can link on unless I remove IMPORTED

set_target_properties(A::A PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" # that's fine
  INTERFACE_LINK_LIBRARIES "A.so;B.so.1.0;C.so.1.0" # that's not fine I need ${_IMPORT_PREFIX}/${CMAKE_INSTALL_LIBDIR}/ before each libs like it did for the headers
)

# generated stuff after

Note that if I remove the IMPORTED in the add_library of the ATargets.cmake and remove the namespace stuff, the target A is correctly accessible in any client cmake project using find_package but it'll NOT link correctly probably because A.so (and B and C) is not referenced using ${_IMPORT_PREFIX}/lib/A.so. I tried adding ${_IMPORT_PREFIX}/lib/ before all libs in INTERFACE_LINK_LIBRARIES, removed the IMPORTED keyword and namespace stuff and guess what, it works perfectly... Now, how do I do that without the trick.

The content of AConfig.cmake.in is:

@PACKAGE_INIT@
include(CMakeFindDependencyMacro)

# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/ATargets.cmake")

# check_required_components(@PROJECT_NAME@) # It is commented because unless I apply the fix specified earlier (IMPORTED and namespace removed), during the find_package call I get:
#################
# CMake Error at /usr/lib/cmake/A/AConfig.cmake:8 # (check_required_components):
#   Unknown CMake command "check_required_components".
#################

That's pretty much it, the rest of this post is implementation details. This code below can be used to solve use case 1 but the export package and cmake config/target files are not correct.

add_library(A
            INTERFACE)

add_library(A::A ALIAS A)


target_include_directories(A
                           INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
                                     "$<INSTALL_INTERFACE:include>")

target_link_libraries(A
                      INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/A.so>"
                                "$<INSTALL_INTERFACE:A.so>"
                                "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/B.so>"
                                "$<INSTALL_INTERFACE:B.so>"
                                "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib/C.so>"
                                "$<INSTALL_INTERFACE:C.so>")

#### install

install(TARGETS  A
            EXPORT   ATargets
            RUNTIME  DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT A_Runtime
            LIBRARY  DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Runtime NAMELINK_COMPONENT A_Development
            ARCHIVE  DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT A_Development)

    install(DIRECTORY   "lib/"
            DESTINATION ${CMAKE_INSTALL_LIBDIR})

    install(DIRECTORY   "${CMAKE_CURRENT_SOURCE_DIR}/include"
            DESTINATION "include")

    write_basic_package_version_file(AConfigVersion.cmake
                                    VERSION "${PACKAGE_VERSION}"
                                    COMPATIBILITY SameMajorVersion)

    install(EXPORT      ATargets
            FILE        ATargets.cmake
            NAMESPACE   A::
            DESTINATION "lib/cmake/A")

    configure_file(AConfig.cmake.in AConfig.cmake @ONLY)
    install(FILES       "${CMAKE_CURRENT_BINARY_DIR}/AConfig.cmake"
                        "${CMAKE_CURRENT_BINARY_DIR}/AConfigVersion.cmake"
            DESTINATION "lib/cmake/A")

# Then some cpack stuff that is not affecting the work done earlier

In a client lib/cmake project after the installation of the generated package (generated by cpack):

find_package(A)

target_link_libraries(my_project PUBLIC/PRIVATE A::A) # Should bring in the headers and link with A B and C

Documentation:

cmake: create a new library target which consists of a prebuilt library

https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/Exporting-and-Importing-Targets

Possible to add an imported library to target_link_libraries that takes care of include directories too?

Exporting an imported library

https://discourse.cmake.org/t/how-to-control-import-prefix-in-exported-targets-cmake-list-file-generated-by-cmake/2291

Create relocatable package with proper autogenerated config cmake

https://discourse.cmake.org/t/exporting-packages-with-a-custom-find-module/3820/2

Thanks for reading/helping

Etienne M
  • 604
  • 3
  • 11
  • "but does not create any target I can link on." - So, what is output of `target_link_libraries(my_project PUBLIC/PRIVATE A::A)` after `find_package(A)` in the client project? – Tsyvarev Aug 27 '21 at 09:53
  • You comment the generated code `INTERFACE_LINK_LIBRARIES "A.so;B.so.1.0;C.so.1.0" # that's not fine`, but it is you who writes `"$"` in your `CMakeLists.txt`, so CMake just generates what you ask for. – Tsyvarev Aug 27 '21 at 09:56
  • I get: Target "my_project" links to target "A::A" but the target was not found. Perhaps a find_package() call is missing for an IMPORTED target, or an ALIAS target is missing? – Etienne M Aug 27 '21 at 09:57
  • Indeed I do "$" but it's also what's done and recommended for the headers. The headers work fine. If I did "${CMAKE_CURRENT_SOURCE_DIR}/lib/A.so" I'd get the same exact absolute path in the Targets.cmake. which is not valid. – Etienne M Aug 27 '21 at 09:59
  • Hmm, the error "... A::A but the target was not found" doesn't correlate with the line `add_library(A::A INTERFACE IMPORTED)` which you observe in the generated `ATargets.cmake` file. You don't forget to include that file in your `AConfig.cmake.in`, do you? – Tsyvarev Aug 27 '21 at 10:02
  • "If I did "${CMAKE_CURRENT_SOURCE_DIR}/lib/A.so" I'd get the same exact absolute path in the Targets.cmake. which is not valid." - So, what is a **valid path** to `A.so` in the installation image (or on the target machine)? CMake has no information about that path, only you knows that. – Tsyvarev Aug 27 '21 at 10:04
  • I added details about AConfig.cmake in the post (I have a weird probleme here too). Regarding what is a valid path, I do not know, the fact is that, under debian cmake create a package that generally put the lib and headers in /usr, but I should not know that. /usr is specified in ${_IMPORT_PREFIX} in ATargets.cmake . With the headers I only tell cmake, where they are in my project and where I want them relative to ${_IMPORT_PREFIX}, I do the same thing for the libs. What else can I do is actually the core of the problem. If I want to stay crossplatform I can't be explicit about that path. – Etienne M Aug 27 '21 at 10:11
  • If you want to **search** libraries on the target machine, then you could use `find_library` and other `find_*` commands in your `AConfig.cmake`. After finding, your script may add resulted paths into the corresponding properties of `A::A` target. – Tsyvarev Aug 27 '21 at 10:19
  • I'm not sure I understand. There is also the problem with add_library(A::A INTERFACE IMPORTED) which is not a valid target. – Etienne M Aug 27 '21 at 12:02

0 Answers0