Search code examples
c++cmakestatic-librariesdependency-management

CMake: Absolute lib-pathnames given to target_link_library( PRIVATE ) are exported, appear in generated INTERFACE_LINK_LIBRARIES. How do I?


In a large CMake/C++ project our major library target Foo is being added. It has many statically linked dependencies that are provided as absolute-paths to target_link_libraries(). Now I'm writing the CMake to export this library once built, including the generated CMake so a library consumer can simply use CMake find_package(). However the generated FooTargets.cmake is embeding the absolute-paths of the link-libraries instead of just the library names.

Additionally, I've already created an add_library(BarCommon INTERFACE), to wrap up the referenced 3rd-party static libs separately and that seems to be working and the client-application consumed it. That accounts for 90% of the libraries also mentioned by FOO. So really FOO's exported INTERFACE_LINK_LIBRARIES should be about 3 file names instead of 40 absolute-pathnames.

  1. I'm confused, why is the target_link_libraries(FOO PRIVATE ${libs} is defining the exported 'INTERFACE_LINK_LIBRARIES' property?

  2. I would like that generated INTERFACE_LINK_LIBRARIES property to be only library filenames, and optimally have finer control over which filenames end up in it. How do I control this?


Code:

Inside a large CMake Macro for generating targets we have this: -

  • I call it to create library/target FOO.
  • _TARGET_ARGS_LIB_DEPENDS will resolve to ABSOLUTE FILE PATHNAMES for about 40 statically linked library dependencies.
add_library(${TargetName} STATIC ${AllSources} ${_TARGET_ARGS_OBJ_FILES})

  <snip>

target_link_libraries(${TargetName}
    PRIVATE
        $<${GCC}:-Wl,--start-group>
        ${_TARGET_ARGS_LIB_DEPENDS}
        $<${GCC}:-Wl,--end-group>
    )

where that longish function returns I have just added these blocks:

target_link_directories(FOO
    INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
        $<INSTALL_INTERFACE:foo/lib>
    )

target_include_directories(FOO
    INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
        $<INSTALL_INTERFACE:foo/include>
    )

install(TARGETS Foo
    EXPORT
        FooTargets
    DESTINATION
        foo/lib
    )    

There is some more 'install' boilerplate at the end of my CMakeLists.txt After I run CMAKE, build the project and run the 'INSTALL' target CMake generated from Visual Studio I can examine generated file: local_install\Release\Windows\Foo\lib\cmake\Foo\FooTargets.cmake shows:

set_target_properties(Nexidia.Workbench.StaticLib.StaticLib PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/Foo/include"
  INTERFACE_LINK_DIRECTORIES "${_IMPORT_PREFIX}/Foo/lib"
  INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:\$<0:-Wl,--start-group>>;C:/full/path/to/libA.lib;C:/full/path/to/libB.lib \$<LINK_ONLY:\$<0:-Wl,--end-group>>"
)

(with the full paths to about 40 libraries).


PS. I'm reading Professional CMake: A Practical Guide, so if someone could cite the chapter/sub-section that answers this it might help.


Solution

  • This is because your library is static and so it cannot be correctly linked to consumers without including its dependencies on consumers' link lines. The same thing happens with the shared dependencies of shared libraries. Static dependencies of shared libraries are fully "absorbed" into the shared library and so they don't appear.

    CMake is aware of all this, and so to respect the other semantics of PRIVATE, it inserts the $<LINK_ONLY:...> generator expression. This ensures that the libraries are used for linking only and that any associated INTERFACE_* properties are not propagated.

    In other words, CMake is behaving correctly here.

    You should resolve this by creating and linking to targets for libB and friends that you can either export as part of your main build or import into both your main build and via find_dependency in your distributed package, along with the libraries themselves, of course.

    The best advice I can give you is to always link to targets in CMake, never to paths or raw library flags.