Add only library headers during target_link_libraries()

Hello,

I have a set of C source files and headers. From this I want to compile a shared library for normal linkage and then similar (just some different DEFINEs) MODULE library used to dynamically load the functionality as a module with RTLD_GLOBAL, so the namespace will be afterwards infused with its symbols.

For modules which depends on symbols from previous modules, I need to create an interface - set of header files. For the normal shared library, this works well as I just set the include directory PUBLIC and every library depending on it with target_link_libraries() will transitively get the headers. But I cannot use the target_link_libraries() call to get the same functionality for MODULE library because in that case that library will be linked in the binary, which cannot be.

One way how to solve this is to create an INTERFACE library with only the PUBLIC headers and target_link_libraries() this to both the shared library and the MODULE library. Any additional module which will depend on this module will just target_link_libraries() the INTERFACE library.

Problem is, I feel this is ugly, and I am making everybody who wants to add another module to the software package to add basically two targets instead of one (not every module will have both shared library and MODULE library version).

So, can I somehow set an option on the MODULE library that when somebody calls target_link_libraries() on it, it will only add the headers, but will not link in any way?

Thanks.

Forgot to mention: This is Linux only question. Probably GNU Libc only too.

1 Like

I wholeheartedly second the request for such a feature. We have a large suite of runtime-loaded libraries in a project. They have usage requirements (all have include directories, sometime have compile definitions too) which we’d love to propagate using CMake’s transitive usage requirement mechanism, but without linking the library itself.

I am aware there’s this possible workaround:

# instead of target_link_libraries(Consumer PRIVATE Library), do
target_include_directories(Consumer PRIVATE $<TARGET_PROPERTY:Library,INTERFACE_INCLUDE_DIRECTORIES>)

However, that needs one such line for each INTERFACE_* property potentially set on the Library target. It could be wrapped in a project-side function to set all such properties, but that then needs maintenance whenever CMake adds a new INTERFACE_* property, and still feels like unnecessary duplication of functionality CMake must have internally anyway.

Can someone enlighten me about the concept of plugin modules where the loader must know about them? Or even need header files or definitions of them. Sounds kinda weird.

In our case, each such module defines some pure virtual interfaces, and then registers itself as "I can create objects which implement I1 and I2" into a central registry. Consumer code uses only the interfaces (so it needs the headers for them), but requests actual objects from the central registry. The registry satisfies the requests based on which modules were loaded at runtime (the runtime loading happens via Qt’s plugin mechanism).

This allows rebuilding a module without the need to re-link everything, and code can make runtime decisions based on which modules were loaded (i.e. which objects are available).

There was an issue where I opined on a $<COMPILE_ONLY> genex for such a use case. I can’t find it right now though.

Hi.

I also think that this should be an option. I initially (wrongfully) thought that this is what INTERFACE linking did. (As it was constantly mentioned in relation with header only libraries).

Our use case is that we compile a couple of static libraries into a single shared library, but we still need to expose some include headers and defines down to consumers, without having the link to those static libraries again, causing issues.

The workaround mentioned by @Angew looks great, but one issue is that its actually not the same as a “header/define only linking” would be. (Same issue over at official documentation: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#transitive-usage-requirements)

target_link_libraries(myExe lib1 lib2 lib3)
target_include_directories(myExe
  PRIVATE $<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>)

Both Petrs and above examples have one specific flaw, which puts it into a hack/workaround section instead of a proper means of doing it - it sets these include directories to the “myExe” target instead of the “lib3”.

If some consuming CMake code would like to get all include directories of myExe, those would then also include lib3’s include directories. (currently facing this issue myself, trying to integrate this into our project. Some bindings generation mechanism now tries to parse the library headers as well…)

Not sure on scope of implementing this (or if there is any proper means of working around it), but I assume CMake could add an option which along scope of linking, also specifies to what to link to.

target_link_libraries(tgt PRIVATE lib1)
target_link_libraries(tgt INTERFACE_NO_LINK lib1)

or something along these lines. Maybe a generator expression, inverse of LINK_ONLY?

One approach I saw and I think will likely pursue in our case is to create another “header-only” library for those static libraries. Then one can link to eg: lib1Headers publically/interface and PRIVATE lib1. I think this should resolve my issue.

Regards,
Martin Peterlin

You mean something like this?

@ben.boeckel

If COMPILE_ONLY stands in for such feature, then yes. Naming wise, I think maybe INTERFACE_ONLY or something along those lines would be better.

If you happen to find this discussion, would gladly read through it:)

How is INTERFACE_ONLY better? Isn’t that just putting things under the INTERFACE scope to begin with?

Not sure - just that COMPILE_ONLY seems like something has to be compiled along. If we only want headers and defines, that seems off?

Why I lean (again, erroneously) to INTERFACE is that header only libraries are defined in such way - thats why I have the association.

Better to have some other naming for such feature.

Yes, those are only used during the compilation phase. Hence the name.

Got it - so is this something you’d be up to have developed and/or is in the process of being developed?

It’s on my wish list, but it is far longer than I can get to. Feel free to submit an MR. I’d suggest tracing how $<LINK_ONLY> works and do something similar for the compile codepath.

Noted - I’ll try looking into it if some free time presents, but will be slow regardless as I’m not familiar with CMake underlying codebase:)

If I do however create any progress, I’ll open the MR just so that there is a single point of development there if it does gain traction.

I know this is an old topic but I faced a similar situation in a large project I’m porting to CMake, so I’ll post my solution in case someone also comes this across.

In our case a set of static libs was linked into a DLL, and that DLL was linked by an EXE. The EXE is also using the static lib headers, but does not link against them directly, but only against the DLL.
The solution I came up with was to create an INTERFACE library for the static libs which only allows actual linking if a certain property is set on the target using the libs.
To create the INTERFACE library I created a function which pulls in the relevant compile only related properties from the static lib, and puts the static library into the INTERFACE_LINK_LIBRARIES of the interface library with a conditional generator expression.

function(add_library_with_interface tgt)
    # the real lib, hidden
    add_library(_${tgt} ${ARGN})
    set_property(TARGET _${tgt} PROPERTY OUTPUT_NAME ${tgt}) # lib name
    set_property(TARGET _${tgt} PROPERTY PROJECT_LABEL ${tgt}) # vs project name
    
    # the interface lib to be used
    add_library(${tgt} INTERFACE)
    foreach(p COMPILE_DEFINITIONS COMPILE_FEATURES COMPILE_OPTIONS INCLUDE_DIRECTORIES SOURCES SYSTEM_INCLUDE_DIRECTORIES)
        set_property(TARGET ${tgt} APPEND PROPERTY INTERFACE_${p} $<TARGET_PROPERTY:_${tgt},INTERFACE_${p}>)
    endforeach()
    target_link_libraries(${tgt} INTERFACE $<$<BOOL:$<TARGET_PROPERTY:${tgt}_DO_LINK>>:$<LINK_ONLY:_${tgt}>>)
endfunction()

# libA is the one for which we would like to prevent linking
add_library_with_interface(libA STATIC a.cpp) # during setup _libA must be used!!!
target_include_directories(_libA PUBLIC incA) # use _libA here

# libB uses libA
add_library(libB STATIC b.cpp)
target_include_directories(libB PUBLIC incB)
target_link_libraries(libB PUBLIC libA)

# libShared uses libB (and transitively libA) and links in libA
add_library(libShared SHARED c.cpp)
target_link_libraries(libShared PRIVATE libB)
set_property(TARGET libShared PROPERTY libA_DO_LINK 1) # causes libA to be linked in

# app uses libShared and libA headers but doesn't link in libA
add_executable(app app.cpp)
target_link_libraries(app libShared libA) # libA is not linked in

$<COMPILE_ONLY> is also be part of this MR.

$<COMPILE_ONLY> has been added in CMake 3.27 [1] [2] and can now be used like this:

target_link_libraries(y $<COMPILE_ONLY:x>)
target_link_libraries(z PRIVATE y)
target_link_libraries(executable z)

In the resuling build, the executable will be linked as c++ -o executable liz.a liby.a without linking against libx.a as well. Include directories of x, if any, will me mentioned only in compilation commands for y and x itself.

Huge thanks to you, Ben, for finally implementing it; I remember quite a few places among my codebases where this might be of service :slight_smile:

1 Like

I got it started. @robert.maynard took it across the finish line :slight_smile: .

1 Like