5

I have a package called MYLIBS consisting of two libraries, lib1 and lib2, which I want to export through the configuration file for the package. The project structure is as follows:

├── Lib1
│   ├── CMakeLists.txt
│   ├── lib1-class.cpp
│   └── lib1-class.h
├── lib2
│   └── CMakeLists.txt
│   ├── lib2-class.cpp
│   ├── lib2-class.h
├── cmake
│   └── LIBSConfig.cmake.in
├── CMakeLists.txt

In lib2 I have:

add_library(lib2
        STATIC
        ${SOURCE_FILES}
        )
target_include_directories(lib2 PRIVATE /path/to/lib1)
target_link_libraries(lib2 PUBLIC lib1)
add_dependencies(lib2 lib1)
install(
        TARGETS
        lib2
        DESTINATION
        lib/MYLIBS/lib2
        EXPORT
        lib2Exports
)
install(
        EXPORT
        lib2Exports
        DESTINATION
        lib/MYLIBS/lib2
)

The same as lib1 except that lib1 does not have the add_dependencies() and target_include/link() as it does not have one.

In my configuration file template, I have:

@PACKAGE_INIT@
## PROJECT_LIBRARIES is filled-in during the package build. in this case : lib1,lib2
set(@PROJECT_NAME@_LIBRARIES @PROJECT_LIBRARIES@)

## The public variables to be used by the client project:
#PROJECT_NAME_INCLUDE_DIRS is all the include paths
#PROJECT_NAME_LIBRARIES is the name of all the libraries

unset(@PROJECT_NAME@_INCLUDE_DIRS)
foreach(INCLUDE_DIR ${INCLUDE_DIRS})
    set_and_check(@PROJECT_NAME@_INCLUDE_DIR ${INCLUDE_DIR})
    list(APPEND @PROJECT_NAME@_INCLUDE_DIRS ${@PROJECT_NAME@_INCLUDE_DIR})
endforeach()

## PACKAGE_PACKAGE_DIRNAME_include is filled-in during the package build
foreach(lib ${@PROJECT_NAME@_LIBRARIES})
    list(APPEND INCLUDE_DIRS @PACKAGE_PACKAGE_DIRNAME_include@/${lib})
endforeach(lib)

# Looks up the information about the exported targets in this package
foreach(lib ${@PROJECT_NAME@_LIBRARIES})
    if(NOT TARGET ${lib})
        include(@PACKAGE_PACKAGE_DIRNAME_lib@/${lib}/${lib}Exports.cmake)
    endif()
endforeach(lib)

So I go through the export files for libraries one by one and include them. The problem is that I have to do that in the right order, i.e. lib1 first and then lib2, otherwise I get an error when reading the configuration file by FindPackage().

I am not really sure how the transitive dependencies would work tbh. Since these libraries are include()ed from the same export file, is there a way of telling CMake about the dependencies in the configuration file or in the export file of lib2 considering that we know where the export files for the dependencies are going to be on the system?

I can see target_link_libraries() has a PUBLIC option. How am I supposed to use that? Would it be of any help?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Maths noob
  • 1,684
  • 20
  • 42

1 Answers1

6

To begin with, you can remove the add_dependencies line. See target_link_libraries and add_dependencies.

Second, you have

target_include_directories(lib2 PRIVATE /path/to/lib1)

But that should not be needed. Instead, remove it, and add this to lib1:

target_include_directories(lib1 PUBLIC /path/to/lib1)

Those are just clean-ups though.

You didn't post the error, and there is lots of other important information missing in your post, so I do some guessing.

I guess the error is something along the lines of

The following imported targets are referenced, but are missing: lib2

You export lib1 and lib2 in two separate 'export sets' - lib1Exports and lib2Exports. Putting them in one 'export set' would solve the problem and be the easiest way forward, at least in the two-target example.

I guess you know that, and you are not doing it because the scale of your build system is bigger than two targets. However, that leads directly to your problem - it means you must manage the order dependencies between 'export sets'.

This is independent of dependencies between targets. An 'export set' is a different 'unit' with an independent dependency graph. CMake doesn't help you to manage it. You have to manage the dependencies between 'export sets'. The problem is that you are not currently managing or expressing those dependencies. See below for your options regarding expressing those dependencies.

target_link_libraries(PUBLIC) does not help you. Read about it in Transitive Usage Requirements.

If you think of an analogy to preprocessor files, you might see your options. Think of lib2_private.h which does not #include lib1_private.h. A alllibs.h will need to include those two in the correct order. Because the _private headers are private, and because clients will always include alllibs.h instead, that will work. In this approach, you manage the total dependency tree in one place.

An alternative approach would be to create lib2_internal.h which contains

#include "lib1_private.h"
#include "lib2_private.h" 

and lib1_internal.h which contains

#include "lib1_private.h"

In this approach, you manage dependencies close to their dependers, so you would have multiple places which specify subsets of the total dependency tree. The alllibs.h could use

#include "lib1_internal.h"
#include "lib2_internal.h" 

or

#include "lib2_internal.h"
#include "lib1_internal.h" 

and the order would not matter.

Your configuration file with the loop is alllibs.h - it is the only file clients include. Can you manage the order entirely there? Yes, if you can manage the order within the @PROJECT_NAME@_LIBRARIES variable. By the way, you should call that @PROJECT_NAME@_EXPORT_SETS probably. If you don't see why, have another look at what I said above about it being a different 'unit'.

You didn't give much information, but I guess you are populating that with multiple

list(APPEND MYPROJ_EXPORT_SETS fooExports)

calls, perhaps in some macro. So the order is not easily maintainable, as it would be as a single set() call.

So, your options to express 'export set' dependencies are:

  1. Manage them in the configuration file - replace the loop with a hardcoded ordered list
  2. Add more variables to express dependencies of export sets wherever you populate the MYPROJ_EXPORT_SETS variable, and replace the loop in your configuration file with something more complex that takes those dependencies into account.
  3. Same as (2), but generate intermediate files and don't care about the include order within the configuration file.

(1) probably makes most sense, but you might also have to step back and think harder about the abstractions/wrappers you're creating which led you here.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
steveire
  • 10,694
  • 1
  • 37
  • 48
  • Thanks for this. I see what you are saying. The only reason I export them individually was that I had difficulty exporting items from the lib1 and lib2 cmake files into the container project and export them from there. see this: http://stackoverflow.com/questions/36037841/cmake-exporting-subproject-targets-to-main-project/36045122#36045122 I would be happy to have a single export file if I can. ( I still want to have separate cmake files for lib1/lib2 though) @PROJECT_NAME@_LIBRARIES is literally the name of the libraries. My intention is to set up a template that wont need manual modification – Maths noob Mar 19 '16 at 22:24
  • 1
    You have a `install(EXPORT)` beside each `install(TARGETS)`. You need to specify the same `EXPORT` in both `install(TARGETS)`, and then use `install(EXPORT)` once with that name. – steveire Mar 19 '16 at 22:30
  • So have install(...Export MYLIBS-export ) in lib1 and lib2 and install(Export MYLIBS-export ) in the container project? – Maths noob Mar 19 '16 at 22:37
  • Great. Thanks. I was wondering, previously since I had multiple export sets for each library I was guarding the include() lines with if(NOT TARGET ${lib}). How would you say is the cleanest way of guarding against the inclusion now? I can of course gaurd against the first library in the list but that's not very elegant. I have seen mentions of pseudo targets in cmake documentation. Do you think using one here would be a good idea? – Maths noob Mar 19 '16 at 22:50
  • 1
    Include the Export file unconditionally in your Config file. If you look into the Export file, you will see that it contains the `if(NOT TARGET)` stuff itself. – steveire Mar 20 '16 at 16:40