If I’m reading the documentation correctly, the difference between the target_link_libraries signature without PUBLIC|PRIVATE|INTERFACE and the one with PUBLIC is that the former propagates the link dependency transitively and the latter does not. That makes me wonder when one wants to use PUBLIC. It seems to me that means in the following, whatever properties of the code cause E to need to link A also cause Y to need to link A, and Y should therefore fail to link, while X links just fine.
if I’m understanding that correctly, it seems like PUBLIC serves no purpose when used with target_link_libraries. In fact it seems like it’s never what you want, and a huge footgun because the developer of B may never try to create two levels of dependents on B. Am I misunderstanding the situation?
# Create (X -> D -> C -> A) and (Y -> E -> B -> PUBLIC A)
add_library(A ...)
add_library(B ...)
target_link_libraries(B PUBLIC A)
add_library(C ...)
target_link_libraries(C A)
add_library(D ...)
target_link_libraries(D C)
add_library(E ...)
target_link_libraries(E B)
add_executable(X ...)
target_link_libraries(X D)
add_executable(Y ...)
target_link_libraries(Y E)
PUBLIC propagates the linking transitively too; that is its primary purpose. The docs say so, albeit in a rather jargon-y way:
Libraries and targets following PUBLIC are linked to, and are made part of the link interface.
The emphasised (by me) part means that the dependency becomes part of the link interface, i.e. anything which links to the consuming target will also link against the PUBLIC dependency.
My rule of thumb is to never use the legacy (i.e. keywordless) signature of target_link_libraries(), and always specify PRIVATE, PUBLIC, or INTERFACE, as applicable.
Is “link interface” described somewhere as having this transitive property? If not, I don’t think the implication holds. Normally an “interface” only applies between adjacent things in a dependency graph. I can find no special definition for “link interface” in the CMake docs.
I don’t know what it means, in the current implementation, to be “in the link interface” of a target but I think if it’s going to be a useful abstraction it would need to propagate differently for static and shared libraries.
The emphasised (by me) part means that the dependency becomes part of the link interface, i.e. anything which links to the consuming target will also link against the PUBLIC dependency.
Yes, that much is clearly implied; what’s not implied AFAICT is that the same applies to things that link to things that link to the consuming target (Y in my example).
My rule of thumb is to never use the legacy (i.e. keywordless) signature of target_link_libraries(), and always specify PRIVATE, PUBLIC, or INTERFACE, as applicable.
Nothing in the docs marks that signature a “legacy” or deprecated and in fact there are other signatures in the target_link_libraries doc that are clearly marked as “legacy,” so I think this signature needs to be viewed as legitimate and still useful. Because one form is not documented in terms of the other, I conclude that its semantics must differ from those of PUBLIC. That’s one of the things that led me to think PUBLIC doesn’t have the transitive property. If I’ve got that wrong, I want to know how the semantics differ.
FWIW, my experiments with this example (you can git clone that URL), lead me to believe that using PUBLIC is indeed equivalent to using the keywordless signature, and works as @Angew describes. Also that the propagation stops at the boundary of any PRIVATE library dependency (you can observe that by PRIVATE-izing D -> C or E -> B).
Maybe the documentation just needs to be updated to clarify all these things. I’d still like authoritative confirmation that my current understanding is correct.
I don’t recall the exact scenario, but my recollection is that in certain specific circumstances, leaving out the keyword can result in effectively getting PRIVATE behavior. It isn’t always going to be PUBLIC. I have the following sentence in my Professional CMake book, but I no longer recall the origins of how I arrived at that conclusion:
In particular, if a project defines a chain of library dependencies with a mix of old and new command forms, the old-style form will generally be treated as PRIVATE.
target_link_libraries(A B)'s affect on A is the same as target_link_libraries(A PUBLIC B). However, if there are multiple calls to target_link_libraries(A ...), they all need to be either plain or keyword. CMP0023 disallowed mixing them. These days the PUBLIC/PRIVATE/INTERFACE keywords are preferred. As discussed in CMake Issue 25981, the target_link_libraries documentation could use an overhaul to present the modern conventions first.