Lesley Lai
Lesley Lai

Reputation: 302

Cmake: Using functions from another target without a direct target_link_libraries

Here is a weird question. I have a "renderer frontend" static target and several "renderer backend" static targets. The "renderer frontend" contains a header file that backends need to implements, and codes in frontend will call them. The backends implement those function by calling underlying their corresponding APIs (for example, OpenGL or DirectX).

So far, so good. I can choose a backend at compile time and link it to the frontend. However, I decide to add unit tests to the frontend which need a "dummy backend" instead of the backend configured for the main program.

So instead of doing this:

if(${RendererBackend} STREQUAL "OpenGL")
    target_link_libraries(Renderer RendererBackendGL)
elseif(...)
    ...
end
target_link_libraries(Application Renderer)

I want to do this:

target_link_libraries(Application Renderer)
target_link_libraries(RendererTest Renderer RendererBackendDummy)
if(${RendererBackend} STREQUAL "OPENGL")
    target_link_libraries(Application RendererBackendGL)
elseif(...)
    ...
end

However, my program has "undefined reference" link error whenever the renderer frontend calls functions that are implemented by backends under GCC. The program runs fine under MSVC though.

Edit:

I checked the linking command, it is like this:

/usr/bin/g++ CMakeFiles/Demo.dir/src/main.cpp.o  -o Demo -rdynamic -lm -ldl /usr/lib/x86_64-linux-gnu/libX11.so -lpthread ../Engine/OpenGL/libRendererBackendGL.a ../Engine/Graphics/libRenderer.a

The problem is "Renderer" library call functions defined in "RendererBackendGL".

Upvotes: 1

Views: 1116

Answers (1)

ComicSansMS
ComicSansMS

Reputation: 54589

You have your dependencies modeled incorrectly.

If Renderer calls functions from RendererBackendGL, you need to model that relationship in CMake:

target_link_libraries(Renderer PUBLIC RendererBackendGL)
target_link_libraries(Application PRIVATE Renderer)

Yes, that implies that everyone linking to Renderer will also need to link against the backend, including the test. But that is a problem of your software architecture, ie. there being calls to backend functions inside Renderer, and not a problem of CMake.

The reason this breaks in gcc is that the order in which input libraries are specified on the linker command line is important. This is not true for MSVC (as long as the lib is present, it will works, no matter if it appears first or last). Since such order-dependencies are toolchain-specific, the only way to make this portable is by explicitly modelling all inter-target dependencies in CMake correctly.

So what can you do now to get around this? Some options that come to my mind:

  • Flip the dependencies. Have the backend depend on the frontend and have an application link only to the backend it wants to use.
  • Move to a plugin concept. Instead of statically linking, the frontend dynamically loads the backend it wants to use from a shared library at runtime.
  • Select a particular backend at compile time. That means, if you still want to build the application with the GL backend and the tests with the dummy backend as part of the same build step, you will need to build the frontend twice. This is probably the approach closest to what you are currently trying to do. You can use object libraries to minimize the amount of recompilation for the frontend, but you will still end up with multiple frontend library targets.

Note that all of these will require some amount of architectural change to your code. So be sure you think all implications through before you start with the refactoring.

Upvotes: 3

Related Questions