Importing dependencies for users of my library

Hi!

I am trying to make my library user friendly, such that a user can import it as a target and link against it in a simple way. My library has a public 3rd party dependency that I can’t seem to configure quite right.

MyLibraryConfig.cmake:

include(CMakeFindDependencyMacro)

find_dependency(MyDependency)
include(${CMAKE_CURRENT_LIST_DIR}/MyLibrary.cmake)

The find_dependency macro invokes FindMyDependency.cmake, which looks like this:

find_library(MyDependency
...
)
...
add_library(MyDependency SHARED IMPORTED)
set_target_properties(MyDependency PROPERTIES 
  IMPORTED_LOCATION ${my_imported_location}
set_target_properties(MyDependency PROPERTIES
  IMPORTED_GLOBAL TRUE
)

MyLibrary.cmake includes, amongst other things, the line:

# Create imported target MyNamespace::MyLibrary
add_library(MyNamespace::MyLibrary SHARED IMPORTED)

set_target_properties(MyNamespace::MyLibrary PROPERTIES
  INTERFACE_INCLUDE_DIRECTORES ...
  INTERFACE_LINK_LIBRARIES "...;libMyDependency.so;..."

In my test project, I do the following:

find_package(MyLibrary REQUIRED)

target_link_libraries(TestProjectLibrary
  PUBLIC
    MyNamespace::MyLibrary
)

CMake doesn’t protest, but during linking I get:

/usr/bin/ld: cannot find -lMyDependency

Any ideas? Am I exporting/installing MyLibrary and its dependencies incorrectly, or is there a problem with how it’s imported?

This should be the dependency target name, not its path directly. How are you using target_link_libraries for it in your project’s code?

The CMake code for my dependency is old and variable-oriented, so my project builds the dependency with ExternalProject_Add and links against it using variables:

target_link_libraries(MyLibrary
    PUBLIC
        $<BUILD_INTERFACE:${MYDEPENDENCY_LIBRARY}
        ....
        $<INSTALL_INTERFACE:${MYDEPENDENCY_LIBRARY_NAME}

where MYDEPENDENCY_LIBRARY_NAME is just the library with the path stripped out (thus the INTERFACE_LINK_LIBRARIES format you quoted).

A lot of that was guesswork, so I’m not surprised to find out that its potentially wrong… what should I do differently?

You should use the target in the main build as well. Using the path will just embed the build-time path into your package. Note that ExternalProject_add doesn’t mix well with add_library in the same project. Generally, you would want each project to build “on its own” and have a “superbuild” that does ExternalProject_add to coordinate building each component in order and passing the relevant information between them.

I have heard that those don’t generally mix well, but I’m stuck with ExternalProject_Add for my dependency due to the aforementioned old-fashioned CMake in their project. I’m not entirely sure “superbuild” is the right approach either, since my customer does not want to or need to build MyDependency - they just need to link against it transitively when linking against MyLibrary.

Anyway - if I can represent my dependency as a target in my main project and link against that target, what’s next? Just add the dependency to my export set?

If you want to use variables everywhere, that’s fine; there should be no need to find the dependency as the full path should be part of your interface directly. However, it will not be relocatable. I would recommend:

  • adding a IMPORTED target for the ExternalProject_add artifacts as you need; and
  • making a FindDependency.cmake module for the dependency that makes a similarly named target (basically just do what that IMPORTED target does, but probably with find_* commands.

That should get you to something which resembles Doing It Right™, but may need tweaking to patch over minor details.

Thanks, appreciate all the feedback on this. I’ll see what I can do!