Static library dependencies

So, let’s say I have separate libraries A, B & C in separate project folders. C is a static library but it’s CMake is unimportant for this example.

B’s CMakeLists.txt looks something like this:

add_library(B STATIC b.cpp)
target_link_libraries(B PRIVATE C)

And A’s CMakeLists.txt looks something like this:

add_library(A STATIC a.cpp)
target_link_libraries(A PRIVATE B)

Now, I know the dependency to C is passed along to A thru B – that makes sense! If I were to add some shared or executable target that linked to A, I know that it needs both B & C’s outputs to link properly. That’s not my issue.

My issue is that when I try to build A, it complains that it can’t find target C. This is the part I don’t understand. Yes, the symbols for C are required ultimately for anyone who uses A, but A doesn’t need C’s symbols. If C is truly private to B then I don’t even need it’s include files! So why do I get a CMake error complaining that it can’t find C when building A? I can’t tell if this is something I don’t understand or if I’m doing something wrong.

If it matters, I’m generating package cmake files as part of my outputs and using find_package to find & load targets. I’m mostly annoyed that I have to add a find_package(C) to A so CMake won’t halt and complain about not finding target C.

Any insight here would be greatly appreciated!

Is it CMake that complains that target C can’t be found or is it the linker complaining that symbols from C’s library can’t be found? Do you have an actual error message (anonymized is fine).

Ok, so it isn’t in building A that you have problems, but with using A via find_package(A)? If this is the case, then yes, B needs to find_package(C) to make its target visible when in a static build.

When a static library has dependencies, the dependencies it links to with PRIVATE are added to the interface wrapped in $<LINK_ONLY>. This basically makes the target not necessary for compilation (no include directories, sources, compile options, definitions, etc.) but it is present for linking. This is because with a STATIC library, the dependent libraries are not actually copied during linking (verification of symbol availability may occur though). So anything using a STATIC library (say, A) needs to also know about these PRIVATE targets in order to properly link in all of the symbols (with A, that would be B and C).

See this VTK MR which hides find_package calls if the target requiring them is SHARED. Previously, all of these had been done in order to satisfy the STATIC build, but it is not necessary if the target needing them is SHARED.

Sorry, I should have used more precise language, and maybe a little more context would make it more obvious what I mean.

We have a collection of libraries we build individually from the command line and there are dependencies like I outlined above. So to continue with the original setup, I’ll generate a project for C with CMake, then do a build & install. Then I’ll do the same for B, but B’s CMakeLists.txt will include find_package(C) and I’d pass the C install directory when generating the project for B. Then I’ll do a build & install of B as well.

Lastly, I’m building static library A as outlined. Again, the CMakeLists for A will have a find_package(B) and I’d pass the install directory for B. This is the part that fails: generating the project for A. I don’t mind providing the full output for the project I’m working on – it wouldn’t exactly give away trade secrets or anything – but I figured a manufactured scenario was simpler. The error message I’d get for A would be something like:

  Target "A" links to target "C" but the
  target was not found.  Perhaps a find_package() call is missing for an
  IMPORTED target, or an ALIAS target is missing?

I can solve this by adding a find_package(C) and also adding its install directory to the A generation step, but it feels like I shouldn’t have to do that. But maybe I’m wrong?

Again, I understand that if I build some executable or shared library app and I link it to A, it will require me to provide both B & C as link targets as well. But since A is static, why is it giving me grief about C?

@ben.boeckel Does my question make more sense now? I’m not sure if you saw my reply. I can try and post a more detailed explanation if necessary.

Issue #19611 sounds like it’s basically my problem.

The reporter is saying it fails on the link step, but I actually get the error during the project generation step. Basically, I can see in my B (from my example above) that it includes a $<LINK_ONLY:C> in the exported config file. That makes sense of course, because if A was an executable or shared library, it would need to link C. But since A is a static library I feel like it should just be passing along the $<LINK_ONLY:C> (presumably in addition to a $<LINK_ONLY:B>) to any consumers of A, not halt generation of A for lack of C.