How to export target which depends on other target which is in multiple export sets?

Problem

I am currently writing CMakeLists.txt files for a larger code project which consists of a hierarchy of different CMake projects which can either be built alone or monolithic all together.

Sadly, I am encountering a limitation of CMake when trying to install my targets and create export/import CMake scripts for it (using the monolithic build):

CMake Error: install(EXPORT "Advanced-Runtime" ...) includes target 
"Advanced-library" which requires target "Fundamentals-library" 
that is not in this export set, but in multiple other export sets: 
lib/cmake/MyProj/Fundamentals-Development.cmake, 
lib/cmake/MyProj/Fundamentals-Runtime.cmake.

An exported target cannot depend upon another target which is exported 
multiple times. Consider consolidating the exports of the 
"Fundamentals-library" target to a single export.

However, that should not really be a problem, because the Fundamentals-Development.cmake export-set only exists for the namelink component of the Fundamentals-library shared-library (and possibly some include files that should be installed, too).
And in fact that only seems to be a problem if I am building the Fundamentals project together with the Advanced project monolithic.
If I cmaked and built the Fundamentals project before, installed its export sets and just then cmake and build the Advanced project (which then uses find_package to retrieve the IMPORTED target for the Fundamentals-library) CMake is pleased and no error occurs.


Details and Example

In order to hopefully better understand what I mean and did I am reproducing the important parts of the CMakeLists.txt files here:

I have a project Fundamentals which internally creates a (shared) library target Fundamentals-library and exports it (as MyProj::Fundamentals). I am creating two export-sets for it, Fundamentals-Runtime and Fundamentals-Development and the built library file will be in the Fundamentals-Runtime export-set and its namelink symlink in the Fundamentals-Development export-set:

...
cmake_minimum_required(VERSION 3.19...3.20)
project( Fundamentals
         VERSION "1.0.0" )

add_library( ${PROJECT_NAME}-library SHARED )
add_library( ${PROJECT_NAME}-library ALIAS MyProj::${PROJECT_NAME} )
...
# Install library
install( TARGETS ${PROJECT_NAME}-library
    EXPORT ${PROJECT_NAME}-Runtime
    LIBRARY
        DESTINATION lib
        COMPONENT   ${PROJECT_NAME}-Runtime
        NAMELINK_SKIP
)
install( TARGETS ${PROJECT_NAME}-library
    EXPORT ${PROJECT_NAME}-Development
    LIBRARY
        DESTINATION        lib
        NAMELINK_COMPONENT ${PROJECT_NAME}-Development
        NAMELINK_ONLY
)

# Install export-sets
install( EXPORT ${PROJECT_NAME}-Runtime
    DESTINATION lib/cmake/MyProj
    FILE        ${PROJECT_NAME}-Runtime.cmake
    NAMESPACE   MyProj::
    COMPONENT   ${PROJECT_NAME}-Runtime
)
install( EXPORT ${PROJECT_NAME}-Development
    DESTINATION lib/cmake/MyProj
    FILE        ${PROJECT_NAME}-Development.cmake
    NAMESPACE   MyProj::
    COMPONENT   ${PROJECT_NAME}-Development
)
...

(Of course, I am also exporting a MyProj-Config.cmake file which internally includes those export-set files shown above. However, for brevity and because it does not help in explaining the problem I am omitting that here.)

I can build this project and then install it just fine.
In the generated import files Fundamentals-Runtime.cmake and Fundamentals-Development.cmake we find an IMPORTED target MyProj::Fundamentals which has no dependencies (aka no property INTERFACE_LINK_LIBRARIES).
(edit: Fundamentals-Development.cmake seems to only contain boiler-plate code and processing returns early at the beginning of the file, because it does not contain any IMPORTED targets.)

I also have a second project Advanced whose CMakeLists.txt has exactly the same install commands, only the beginning of the file is slightly different:

cmake_minimum_required(VERSION 3.19...3.20)
project( Advanced
         VERSION "2.0.0" )

# import dependency if not currently building it already.
if (NOT TARGET MyProj::Fundamentals)
    find_package( MyProj REQUIRED COMPONENT Fundamentals )
endif()

add_library( ${PROJECT_NAME}-library SHARED )
add_library( ${PROJECT_NAME}-library ALIAS MyProj::${PROJECT_NAME} )
# Add (public) dependency.
target_link_libraries( ${PROJECT_NAME}-library
    PUBLIC MyProj::Fundamentals )
...
# Install library and export-set as before
...

As one can see the target Advanced-library has a dependency on the MyProj::Fundamentals target created in project Fundamentals.

As I already said above in the problem description, building both projects together in a monolithic build yields the error. Only building both projects separately and using the IMPORTED target for MyProj::Fundamentals from the Advanced project works just fine.

When doing that I can see that the IMPORTED target MyProj::Advanced indeed has a property INTERFACE_LINK_LIBRARIES with MyProj::Fundamentals as its value. And this is what I want.
But sadly, that seems to be the problem with the monolithic build.

For example, I can workaround the error by changing the target_link_libraries line above to the following:

target_link_libraries( ${PROJECT_NAME}-library
    PUBLIC $<BUILD_INTERFACE:MyProj::Fundamentals> )

However, that results in the IMPORTED target MyProj::Advanced to not carry its dependency information, which is suboptimal.

Another workaround would be do not install the namelink in a separate export-set. But that is what I want to do and that is what the NAMELINK_ONLY and NAMELINK_SKIP options of install(TARGETS ...) is there for in the first place.


Questions

Does anyone have a good suggestion how to circumvent that error and still be able to build monolithic and still have the exported Advanced-library carry its dependency information?

Or does anyone know where and how to change the implementation of CMake to not trip over this situation and prevent that error in this case?

1 Like

I think the issue is that we attach the full target to each export here. I suspect that for this setup:

# Install library
install( TARGETS ${PROJECT_NAME}-library
    EXPORT ${PROJECT_NAME}-Runtime
    LIBRARY
        DESTINATION lib
        COMPONENT   ${PROJECT_NAME}-Runtime
        NAMELINK_SKIP
)
install( TARGETS ${PROJECT_NAME}-library
    EXPORT ${PROJECT_NAME}-Development
    LIBRARY
        DESTINATION        lib
        NAMELINK_COMPONENT ${PROJECT_NAME}-Development
        NAMELINK_ONLY
)

we want to not put the full target into each export.

@kyle.edwards This feels like its somewhat in your wheelhouse (though I might be wrong). How feasible is it to just Do The Right Thing (via a policy) here or should we do:

  • recommend not adding EXPORT on the NAMELINK_ONLY install rule (my gut feeling for the actual right answer here)
  • add a new keyword EXPORT_INCREMENTAL to manually do this?

I realized for my specific case, where the Fundamentals-Development export-set does not really contain any information, that the easiest solution really is to omit the EXPORT ${PROJECT_NAME}-Development option, possibly by combining both INSTALL( TARGETS ... ) commands into one:

install( TARGETS ${PROJECT_NAME}-library
    EXPORT ${PROJECT_NAME}-Runtime
    LIBRARY
        DESTINATION lib
        COMPONENT   ${PROJECT_NAME}-Runtime
        NAMELINK_COMPONENT ${PROJECT_NAME}-Development
)

However, if I have two separate install( TARGETS ... ) commands as in my original example and the second one does install an additional target, I cannot omit the EXPORT option.
In this situation it would be great if CMake would “just Do The Right Thing”.

I usually either try to export each target individually or as a single batch myself. I think this is a suitable solution for now; I don’t forsee this getting much attention in the near future since the workaround is pretty simple (even in your case, splitting into separate install() calls for targets that act differently is also possible). The main issue is that 1) deciding (on the details of), 2) implementing, and 3) documenting the Right Thing™ is not a trivial task here.