How do I configure a library so that the projects which depend upon it do not need to know about the projects upon which it depends?

I have Project A, a library, and Project B, an executable which depends on A. A and B are completely separate with regard to directory structure, build systems, etc. A depends on a third party library, X. As it happens in my case, A, B, and X all use CMake, but A is the only project under my control. How can I configure CMake for A so that B does not need to know about X?

For the moment, B gets a linker error, and the only way that I can find to make that go away is to have B reference X, which I don’t want to do. I have created a minimal example which recreates my problem. X happens to be an open source project called pugixml.

Here is file CMakeLists.txt for A:

cmake_minimum_required(VERSION 3.18)

project(projA)

add_library(projA proja.cpp proja.hpp)
target_include_directories(projA PUBLIC pugixml-1.12/src)
target_link_directories(projA PUBLIC pugixml-1.12/build/Debug)
target_link_libraries(projA PUBLIC pugixml)

add_executable(projATest projatest.cpp)
target_link_libraries(projATest PRIVATE projA)

I can provide the C++ code if you want, projatest.cpp is just a copy of some example pugixml code. This compiles and links fine and the tests run and pass. Interestingly, projATest knows nothing about pugixml.

Here is file CMakeLists.txt for B:

cmake_minimum_required(VERSION 3.18)

project(projB)

add_executable(projB projb.cpp)
target_include_directories(projB PRIVATE C:/projects/pugixml/projA)
target_link_directories(projB PRIVATE C:/projects/pugixml/projA/build/Debug)
target_link_libraries(projB PRIVATE projA)

Under visual studio, compilation fails with “error LNK2019: unresolved external symbol” relating to pugixml.

How can I configure the CMake file for Project A such that Project B does not need to know about pugixml?

You should generate a config file for A and consume it from B via find_package(A). Instead of hard-coding paths, you should instead use find_package(pugixml) and consume it through its imported target (pugixml::pugixml IIRC). A’s config file should then do find_package(pugixml) on its own so that B doesn’t need to do it.

Many thanks for getting back to me.

A and B are proprietary projects, internal to our organization. A is always installed to a standard path known to B. So find_package(A) seems like overkill to us - why search for something whose location is known? What I am hoping for is some kind of incantation which will allow me to build A such that B depends only on A and not on X (pugixml).

I take it that I am not using cmake in the way that it is intended to be used, and that there is no canonical solution to my problem.

Use a static pugixml and a shared A. Anything else needs pugixml on the link line when consuming A itself. It’s just the way linking works on Windows (AFAIK, there’s no --whole-archive equivalent).

Many thanks for your help.

I would like to build pugixml as a static library, A as a static library, and B as an executable, where B links to A but does not need to link to pugixml. Absolutely this is possible with Visual Studio under Windows. And it is not equivalent to --whole-archive. A links not all of pugixml but only those symbols from pugixml which A requires.

I have created a project that does what I want using nmake files.

Here is the nmake file for pugixml:

ALL : pugixml.lib

pugixml.o: pugixml.cpp
 cl /c /EHsc /Fopugixml.o pugixml.cpp

pugixml.lib: pugixml.o
 link /lib /out:pugixml.lib pugixml.o

clean:
 del pugixml.o pugixml.lib

Here is the nmake file for A:

ALL : projatest.exe

PUGIXML_INC = pugixml-1.12\src
PUGIXML_LIB = pugixml-1.12\src\pugixml.lib

proja.o: proja.cpp
 cl /c /EHsc /Foproja.o /I$(PUGIXML_INC) proja.cpp

proja.lib: proja.o $(PUGIXML_LIB)
 link /lib /out:proja.lib proja.o $(PUGIXML_LIB)

projatest.exe: proja.lib projatest.cpp
 cl projatest.cpp proja.lib

clean:
 del proja.o proja.lib projatest.obj projatest.exe

Here is the nmake file for B:

projb.exe: ..\proja\proja.lib projb.cpp
 cl /I..\proja projb.cpp ..\proja\proja.lib

clean:
 del projb.obj projb.exe

How do I achieve the equivalent result using cmake?

There is one other anomaly that I would like to point out. If you refer back to my original post: projATest links to projA, but not to pugixml. projATest builds, links, and runs fine. projB, which does the same thing as projATest, fails to link. Because projATest is in the same CMakeLists.txt file as projA, CMake is doing something magical to projATest to allow it to link agains projA only and not pugixml.

If B does not need plugixml, why are those lines PUBLIC instead of private?

Neither projB nor projATest (should) need pugixml.

With those 3 lines PUBLIC:

  • projATest builds, links, and runs as I expect
  • projB fails to link

With those 3 lines PRIVATE, projATest also fails to link.

Unless you have some way of A copying the symbols from the pugixml.lib that it needs, the final executable will still need it on the command line.

But where do they come from? Are the copied into A’s .lib directly?

Yes, it knows that static transitivity requires pugixml to be on the link line for projATest.

Compare the link lines for projATest in these two cases. One will have pugixml on the link line and the other will not.

Yes. So with those nmake files that I provided above, I have implemented a very simple and common use case which behaves exactly as I expect. How can I accomplish that with cmake?

IIRC, this is not how CMake links static libraries. AFAIK, dependent libraries will not be added to the link line here. Can you make a verbose build to see the link line that CMake generates?

CMake has its own model of how libraries work so that there’s some consistency between platforms and generators. This issue is likely what you want to track here.

lib.exe /out:projA.lib @CMakeFiles\projA.dir\objects1.rsp

So you are right, the link step that cmake generates for projA does not pull in pugixml.

The link that you provided looks similar to the problem that I am facing - although I want to link in not all of pugixml but rather only those symbols required by projA. This is what I achieved with NMAKE (lib.exe).

Is there a simple way to pass additional flags in to cmake to get the behavior that I want? I guess not.

In the course of my troubleshooting, I noticed another oddity. Referring back to the cmake files that I provided in my original post - if I generate these for the Visual Studio GUI, then at least projATest builds, links, and executes as I expect - even though projB does not. If I generate for nmake, then even projATest does not link.

So it seems that your comment above…

Yes, it knows that static transitivity requires pugixml to be on the link line for projATest.

…applies only to the Visual Studio generator and not to the nmake generator. It seems wrong to me that these two would behave differently.

In any case thanks again for your help.

I agree. I’m not that familiar with the VS generators to know what kinds of restrictions are forced upon us by MSBuild’s semantics.