Propagating usage requirements to object libraries

Hi all,

I’m seeing some rather surprising behaviour and would greatly appreciate if someone could explain it to me: this is a 3rd party library that sets the CMake minimum version at 3.12 (I’m using 3.20.5).

The ‘issue’ is caused by this particular line of code:

target_link_libraries("${NAME}" INTERFACE "${lib}")

Here, ${NAME} represents an object library, and ${lib} is an imported target defined by an external dependency (TBB, defining a TBB::tbb target, an imported shared library). The linker aspect seems to work (and propagate) correctly, the problem is with include directories.
If I execute the following code right after the call to target_link_libraries():

get_property(tgtIncs TARGET "${NAME}" PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
get_property(libIncs TARGET "${lib}" PROPERTY INTERFACE_INCLUDE_DIRECTORIES)

What I get for tgtIncs is: $<INSTALL_INTERFACE:include>;A;B
What I get for libIncs is: C
where A, B, and C are some absolute paths pointing to completely different areas on disk.

Now, I would expect that after calling target_link_libraries(), tgtIncs should contain $<INSTALL_INTERFACE:include>;A;B;C – but C is missing and I have to explicitly call:

target_include_directories("${NAME}" INTERFACE "${libIncs}")

to make sure it gets propagated to the object library.

I would think that this should happen automatically when calling target_link_libraries. Am I misunderstanding something? Now, I’ve already managed to get this working, I’m merely looking for clarification on what is the expected behaviour here…

Thanks,
Z.

The usage requirements that are inherited from target_link_libraries is a generator time thing. This is a time in CMake processing after all of the CMake code has been read and CMake is writing out the build files. So these inherited usage requirements are not available at configure time (in fact, one could remove ${lib} from the link libraries and if so, the other usage requirements would have to be “backed out”, but they may have been manually updated elsewhere in the meantime too. Today, the ${lib} could even be modified after that point which makes it very confusing if you had to consider ${lib} at point A being different than at point B.

So those properties can’t be reliably queried at configure time? Fair enough. But I can observe this behaviour in generated projects as well – without that extra call to target_include_directories() the target that links ${NAME} ends up missing the include path C and the compilation fails. With that additional call, the C path is added to the include paths for the target that links ${NAME} and everything works.
Does that mean that the call to target_include_directories as shown in my example code is also unreliable, and one should rather use something like:

target_include_directories("${NAME}" INTERFACE $<TARGET_PROPERTY:${lib},INTERFACE_INCLUDE_DIRECTORIES>)

?

Can you give a small example which exhibits the problem instead of abstract placeholders? I suspect some target is using PRIVATE where it shouldn’t or something.

I don’t think that’s feasible – it’s just more effort than it’s worth . But that doesn’t really matter. You’ve already given me the answer. Your explanation and quick peek at the source code made it finally click.

As suspected, it was just me misunderstanding how this works – when target_link_libraries() is called, ${lib} just gets appended to the list of linked libraries kept by ${NAME}, that’s it, there’s no property propagation happening, unless I do it manually – so I shouldn’t really expect to see any changes to INTERFACE_INCLUDE_DIRECTORIES – I need to call target_include_directories myself for that to happen (as demonstrated).

Thanks!

If ${lib} has an INTERFACE include directory, it should be added during the generator step. If it doesn’t have such a usage requirement, well, then it is up to the consumer to also do that. But generally, all that should be needed to consume an imported target is target_link_libraries.