LINK_LIBRARY generator expression with WHOLE_ARCHIVE doesn't work with exported/imported targets

Context of the Problem

We are creating a DPDK package using CMake that exports targets with a namespace prefix (e.g., dpdk::libeal, dpdk::libmempool_ring). Some DPDK libraries (for example, mempool_ring and mempool_stack) require linking with the --whole-archive flag (or -Wl,--whole-archive), otherwise symbols from these libraries are not included in the final executable.

The Problem

We are trying to use the CMake generator expression $<LINK_LIBRARY:WHOLE_ARCHIVE,...>, but it doesn’t work properly for exported targets:

  1. When using local target names (before export):

    cmake

    target_link_libraries(some_target INTERFACE 
        "$<LINK_LIBRARY:WHOLE_ARCHIVE,libmempool_ring>"
    )
    

    After exporting the package, when targets receive the dpdk:: prefix, this expression starts interpreting libmempool_ring as a library name (not a target) and tries to link with -llibmempool_ring.

  2. When using $<TARGET_NAME:...>:

    cmake

    target_link_libraries(some_target INTERFACE 
        "$<LINK_LIBRARY:WHOLE_ARCHIVE,$<TARGET_NAME:libmempool_ring>>"
    )
    

    In this case, CMake correctly resolves the library path (the correct path to librte_mempool_ring.a), but the --whole-archive flags are not added. The $<TARGET_NAME:...> expression appears to return the path to the library file, not a reference to the target, which causes the loss of information about the need for WHOLE_ARCHIVE linking.

Expected Behavior

When linking, something like this should be generated:

text

-Wl,--whole-archive /path/to/librte_mempool_ring.a -Wl,--no-whole-archive

Actual Behavior

  1. In the first case, an incorrect linker flag is generated: -llibmempool_ring

  2. In the second case, only the library path is generated without the --whole-archive flags

Questions

  1. How to properly use $<LINK_LIBRARY:WHOLE_ARCHIVE,...> for targets that will be exported with a namespace prefix?

  2. Is there a workaround for this issue in current CMake versions (4.2.1)?

  3. Is this a known issue/limitation in CMake?

CMake Version and Environment

  • CMake 4.2.1 (minimum version 3.30 specified in the project)

  • Compiler: GCC/G++

  • Linker: GNU ld or gold

  • OS: Linux

Your explanations are not clear enough. Please, provide a snippet showing the problem.

I’m building DPDK via ExternalProject, then creating CMake targets for all libraries, but I can’t get --whole-archive to work properly when linking in the final project.

  1. Building DPDK via ExternalProject:
externalproject_add(dpdk_build ...)
  1. Creating dummy targets for all DPDK libraries:
foreach(lib ${ALL_LIBS})
    add_library(${lib} STATIC EXCLUDE_FROM_ALL "${STUB_C}")
    # Setting properties...
endforeach()
  1. Attempts to add whole-archive (tried 3 methods):

Method 1: In the list of libraries in a group:

set(_LIBS
    "lib1"
    "-Wl,--whole-archive"
    "lib2"  # Should be in whole-archive
    "lib3"  # Should be in whole-archive  
    "-Wl,--no-whole-archive"
)

Problem: The flags appear separately from the libraries in the link command line.

Method 2: Using LINK_LIBRARY with TARGET_NAME:

target_link_libraries(lib2 INTERFACE 
    "$<LINK_LIBRARY:WHOLE_ARCHIVE,$<TARGET_NAME:lib2>>"
)

Result: Library paths are substituted correctly, but the --whole-archive flag does not appear during linking.

Method 3: Using LINK_LIBRARY without TARGET_NAME:

target_link_libraries(lib2 INTERFACE
    "$<LINK_LIBRARY:WHOLE_ARCHIVE,lib2>"
)

Result: The --whole-archive flag appears in the command line, but CMake tries to link with -llib2 instead of using the library file path. It doesn’t use the target lib2, but tries to find the library by name in standard paths.

  1. Grouping libraries:
add_library(core_libs INTERFACE)
foreach(lib ${CORE_LIBS})
    target_link_libraries(core_libs INTERFACE ${lib})
endforeach()
  1. In the consuming project:
target_link_libraries(my_app dpdk::core_libs)

Paradoxical Situation

It turns out that in methods 2 and 3, different things work, but not together:

  • Method 2: Correct library paths, but no --whole-archive

  • Method 3: Has --whole-archive, but incorrect paths (uses -l instead of absolute paths)

Here’s what the link commands look like:

Method 2:

# Paths are correct, but no whole-archive
/path/to/librte_net_af_packet.a /path/to/librte_net_bnx2x.a ...

Method 3:

# Has whole-archive, but link is incorrect
-Wl,--whole-archive -lrte_net_af_packet -Wl,--no-whole-archive ...

$<LINK_LIBRARY> generator expression is not supported and will be ignored when specified on an INTERFACE library type, as clearly specify in the documentation, under the sub-title The following limitations should be noted:.

How can I configure an exported target (static library) so that when it’s imported, it links with the whole-archive attribute? It’s not correct to maintain this option in projects. I would like consumers to simply specify the dependency as:

target_link_libraries(my_app 
    dpdk::core_libs 
    dpdk::pmds
)

If I use PUBLIC instead of INTERFACE in the package build script:

target_link_libraries(lib2 PUBLIC 
    "$<LINK_LIBRARY:WHOLE_ARCHIVE,$<TARGET_NAME:lib2>>"
)

The problem occurs at the configuration stage - error: the library cannot link to itself.

You cannot do that. The link features, defined by the genex $<LINK_LIBRARY>, must be specified by the consumer, not the producer.

DPDK consists of (2-)300+ libraries. Some of them require linking with specific flags. They also have interdependencies. All drivers must be linked with whole-archive, otherwise the linker completely discards the driver code. Some libraries require linking with this and other flags.

We have over 40 modules, each linked with its own list of DPDK libraries.
5 projects.
At this scale, maintaining and verifying the correctness of linking is a very labor-intensive task, and there have been many bugs. Therefore, an attempt was made at this refactoring…

Is it possible to submit a feature request, please? In any form, so that we can manage linking parameters and the consumer wouldn’t even need to know that some of the libraries they import into their project require special linking?

You can use this pattern:

add_library(lib STATIC ...)

add_library(w_lib INTERFACE)
target_link_libraries(w_lib INTERFACE "$<LINK_LIBRARY:WHOLE_ARCHIVE,lib>")

add_executable(main ...)
target_link_libraries(main PRIVATE w_lib)

It looks like it should work, but it doesn’t:

dpdkTargets.cmake:

...
# Create imported target dpdk::libmempool_ring
add_library(dpdk::libmempool_ring STATIC IMPORTED)

set_target_properties(dpdk::libmempool_ring PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
  INTERFACE_LINK_LIBRARIES "\$<LINK_LIBRARY:WHOLE_ARCHIVE,dpdk::libmempool_ring>;dpdk::libeal;dpdk::libmempool"
)
...

When imported into a project and linked, the resulting link command looks like this:

/sbin/c++  ...  /home/pavard/.build-Linux-x86_64/dpdk-25.11/lib/librte_mempool_ring.a ...

Without the necessary arguments.

Tried manually removing the escape backslash before $ – didn’t help.

As I already said: you cannot specify the $<LINK_LIBRARY> on the producer. Moreover, you specify to link dpdk::libmempool_ring to itself by specifying $<LINK_LIBRARY:WHOLE_ARCHIVE,dpdk::libmempool_ring> as part of libraries linked to dpdk::libmempool_ring.

My mistake: mempool_ring is part of the drivers list, not the libraries list. Consequently, I was experimenting with the wrong list. Your approach worked, but $<TARGET_NAME:> still needs to be written there. Thank you very much for your help.