Static library link order changes when more than one target from the same cycle is specified in target_link_libraries
It's a bit of an obscure scenario, that I have nevertheless succeeded in encountering.
Complete sample project: static_cycle_mini.zip
Excerpt:
// core.cpp
int core_f() { return 1; }
// gui.cpp
#include "gui.h"
int gui_f() { return core_f(); }
// gl.cpp
#include "gl.h"
int gl_f() { return gui_f(); }
// main.cpp
#include "gl.h"
int main(int argc, char* argv[]) {
gl_f();
return 0;
}
// core.h
#pragma once
int core_f();
// gui.h
#pragma once
#include "core.h"
int gui_f();
// gl.h
#paragma once
#include "gui.h"
int gl_f();
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(static_cycle_order_issue LANGUAGES CXX)
add_library(Core core.cpp)
add_library(Gui gui.cpp)
add_library(Gl gl.cpp)
target_link_libraries(Gui PRIVATE Core)
target_link_libraries(Gl PRIVATE Gui)
target_link_libraries(Core PRIVATE Gl)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE
Core
#Gui # <- breaks if uncommented
)
# Working link line
$ /usr/bin/c++ CMakeFiles/app.dir/main.cpp.o -o app libCore.a libGl.a libGui.a libCore.a libGl.a libGui.a
# Broken link line when Gui is uncommented
$ /usr/bin/c++ CMakeFiles/app.dir/main.cpp.o -o app libCore.a libGui.a libGl.a libCore.a libGui.a libGl.a
libGui.a(gui.cpp.o): In function `gui_f()':
gui.cpp:(.text+0x5): undefined reference to `core_f()'
In this project, there is no cyclic dependency in the .h / .cpp files, just at the CMake target level, Gl -> Gui -> Core -> Gl
. Clearly there should be no cyclic dependency on the target level either if there is none at the cpp level, but it's there to demonstrate the issue of ordering at the link line and in target_link_libraries()
.
The issue is that when the app links against any single dependency of the cycle (so target_link_libraries(app PRIVATE Core)
or target_link_libraries(app PRIVATE Gui)
or target_link_libraries(app PRIVATE Gl)
, the generated link line is valid, and successfully links the application binary.
But if the app links against 2 of the cycle dependencies (over-specification of dependencies), failure can happen depending on the order of targets in the target_link_libraries(app PRIVATE ...)
call.
target_link_libraries(app PRIVATE Core) # works -> libCore.a libGl.a libGui.a libCore.a libGl.a libGui.a
target_link_libraries(app PRIVATE Gui) # works -> libGui.a libCore.a libGl.a libGui.a libCore.a libGl.a
target_link_libraries(app PRIVATE Gl) # works -> libGl.a libGui.a libCore.a libGl.a libGui.a libCore.a
target_link_libraries(app PRIVATE Core Gui) # broken -> libCore.a libGui.a libGl.a libCore.a libGui.a libGl.a
target_link_libraries(app PRIVATE Gui Core) # works -> libGui.a libCore.a libGl.a libGui.a libCore.a libGl.a
target_link_libraries(app PRIVATE Gl Gui) # works -> libGl.a libGui.a libCore.a libGl.a libGui.a libCore.a
target_link_libraries(app PRIVATE Core Gl Gui) # works -> libCore.a libGl.a libGui.a libCore.a libGl.a libGui.a
target_link_libraries(app PRIVATE Core Gui Gl) # broken -> libCore.a libGui.a libGl.a libCore.a libGui.a libGl.a
....
Note the failing linking behaviour can only be observed on Linux with the regular GNU ld linker (because static library order matters there, as opposed to say macOS).
Is this intended unspecified behavior? Could CMake be smarter in this case, by perhaps repeating the libraries one more time if a cycle dependency is specified more than once in the consuming target? Is this strange re-ordering expected?
I'm aware of LINK_INTERFACE_MULTIPLICITY
, but I don't think it should be the consuming project's responsibility to set that when using imported static libraries that form a cycle.
Aka if I find_package()
and link to a static library that is part of a cycle, it shouldn't matter which of the targets I specify from the cycle (even if i over-specify them), as long the imported targets had correct dependencies amongst themselves?