Encapsulating Link Arguments

Hey CMake community,

I’m trying to figure out how to encapsulate linker flags in one of the subdirectories of my cmake project. Is there a way for be to set link flags on a target so that when it is linked against, those flags are added to the link command? Specifically by wrapping the target library with -Wl flags?

I have a rather large, overly complicated build that I’m trying to clean up a little bit by encapsulating some of the build components into their own cmake. At first I was defining object libraries, but I found the use of target_include_directories and target_link_libraries very convenient for defining those things on static libraries.

I have one component that defines symbols that need to be kept in the final binary (dynamic library) because they’re JNI symbols and even though they aren’t referenced in c++ code, they will be used by java code. Those symbols are defined in one component that is now being linked as a static library. Without additional flags, all those JNI symbols get stripped out when doing the final link. Furthermore that library has other dependencies that don’t need to be forced to be in the final binary.

The solution I’ve found with my current structure is the following

add_subdirectory(jni_component_dir)
add_library(final_lib SHARED ${final_lib_src})
target_link_libraries(final_lib
                      -Wl,-whole-archive
                      jni_component
                      -Wl,-no-whole-archive
                      ... other libs too ... )

With this structure the linker command looks like ld ... -whole-archive jni_component -no-whole-archive dep1 dep2 .... Hopefully you get the idea.

This actually works, but now I wonder if I can move this awareness to the jni directory itself. Afterall, that component is responsible for knowing if it has “unused” symbols that need to be kept.

If I try to move the linker flags to the subdirectory then I get something more akin to this:

add_library(jni_component_helper STATIC ${jni_src})
target_link_libraries(jni_component_helper dep1 dep2)
add_library(jni_component STATIC "")
target_link_libraries(jni_component 
                      -Wl,-whole-archive
                      jni_component_helper
                      -Wl,-no-whole-archive)

which produces ld ... -whole-archive jni_component jni_compent_depa dep1 dep2 dep1_dep -no-whole-archive ... and ultimately gives me link errors with duplicate symbols.

What I want is something similar to this but without all the dependent libraries getting pulled into that link commands. It seems like subdirectories imposes some sort of boundary where the link flags are treated differently. Is there any way to impose this kind of boundary on a target without requiring another subdirectory? Furthermore, is there any way to componentize this without cluttering the top-level cmake?

First, it is not a good practice to specify link options through target_link_libraries() command. It is preferable to use target_link_options().
And also, it is strongly recommended using the up-to-date syntax regarding target_link_libraries() by specifying one of following keyword PUBLIC, PRIVATE or INTERFACE.
And to finish, to avoid unexpected de-duplicated link options, it is required to use the SHELL: prefix.

Here is possible approach:

add_library(jni_component_helper STATIC ${jni_src})
target_link_libraries(jni_component_helper PRIVATE dep1 dep2)

add_library(jni_component STATIC "")
# add dependency of jni_component over jni_component_helper
target_link_libraries (jni_component PRIVATE  jni_component_helper)
# ensure the required link option is specified for the helper library only
target_link_options(jni_component INTERFACE "SHELL:-Wl,-whole-archive $<TARGET_FILE:jni_component_helper> -Wl,-no-whole-archive")

And for the final step:

add_subdirectory(jni_component_dir)
add_library(final_lib SHARED ${final_lib_src})
target_link_libraries(final_lib PRIVATE jni_component ... other libs...)
1 Like