Quick answer: Use target_link_libraries(target PRIVATE depA depB) where dependents come first. CMake handles order; GCC’s old linker is positional.
A custom engine builds on Windows MSVC fine. Linux GCC: hundreds of undefined references to GLFW symbols. The link order is wrong.
Modern target_link_libraries
add_library(EngineCore STATIC src/...)
target_link_libraries(EngineCore PRIVATE glfw glm)
add_executable(Game src/main.cpp)
target_link_libraries(Game PRIVATE EngineCore)
CMake propagates dependencies via PUBLIC/INTERFACE/PRIVATE. EngineCore->glfw is private; Game depends on EngineCore, gets glfw transitively.
PUBLIC vs PRIVATE
- PRIVATE: dependency used only inside this target’s implementation.
- PUBLIC: also used in headers; propagates to anyone linking this target.
- INTERFACE: only for header-only libs; consumers get the dep.
If your engine’s headers reference GLFW types, use PUBLIC. Else PRIVATE for cleaner dep graph.
Static Library Order on GCC
For static libs with cycles between them, wrap in --start-group / --end-group:
target_link_libraries(Game PRIVATE
-Wl,--start-group
EngineCore EngineNetwork EngineAudio
-Wl,--end-group
)
Linker scans group repeatedly until all symbols resolved.
find_package vs Subdirectory
Mixing system-installed packages with vendored deps can confuse. Pick one strategy:
find_package(glfw3 REQUIRED)
target_link_libraries(EngineCore PRIVATE glfw)
Or use add_subdirectory for vendored. Don’t mix for the same dep.
Verifying
Build on Linux/MinGW/MSVC. All resolve. No undefined references. Static analysis (--print-files) shows expected link order.
“Modern CMake handles order if you use targets correctly. Migrate from raw link flags.”
For complex engines, generate a graphviz of your CMake target deps via --graphviz=deps.dot — reveals accidental couplings.