CMake unit testing - Unresolved external symbol - c++

I am rather new to C++ and CMake. I'm trying to make a library, and I'm getting the errors when I try to run my unit tests. From my research I've already gathered what the "Unresolved external symbol" errors mean, but I'm unable to figure out how to fix it.
Here's my project structure:
lib
glfw - GLFW source folder
src
ogl-renderer.cpp
ogl-renderer.h
CMakeLists.txt
...additional source files
test
ogl-test.cpp
ogl-test.h
CMakeLists.txt
CMakeLists.txt
CMakeLists.txt:
cmake_minimum_required (VERSION 3.8)
project("ogl-renderer")
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory ("lib/glfw-3.3.2")
add_subdirectory ("src")
add_subdirectory ("test")
enable_testing()
add_test (ogl-renderer olg-test)
src/CMakeLists.txt:
cmake_minimum_required (VERSION 3.8)
add_library (ogl-renderer "engine/renderer.cpp" "engine/renderer.h" "engine/renderer.cpp" "engine/renderer.h" "engine/Window.cpp" "engine/Window.h" "engine/Shape.cpp" "engine/Shape.h" "engine/message-queue.cpp" "engine/message-queue.h" "engine/messages/window-mgmt.h" "engine/messages/window-mgmt.cpp")
target_link_libraries(ogl-renderer glfw)
test/CMakeLists.txt:
add_executable (ogl-test "ogl-test.cpp" "ogl-test.h")
target_link_libraries(ogl-test ogl-renderer)
test/ogl-test.cpp:
#include "../src/ogl-renderer.h"
void testWindow() {
NglRenderer::startRenderer();
int windowId = NglRenderer::createWindow("Test", 640, 480);
}
int main() {
testWindow();
}
Errors:
Error LNK2019 unresolved external symbol "void __cdecl NglRenderer::startRenderer(void)" (?startRenderer#NglRenderer##YAXXZ) referenced in function "void __cdecl testWindow(void)" (?testWindow##YAXXZ) C:\Users\chansen\source\repos\ogl-renderer\out\build\x64-Debug\ogl-renderer C:\Users\chansen\source\repos\ogl-renderer\out\build\x64-Debug\ogl-test.cpp.obj 1
Error LNK2019 unresolved external symbol "int __cdecl NglRenderer::createWindow(char *,int,int)" (?createWindow#NglRenderer##YAHPEADHH#Z) referenced in function "void __cdecl testWindow(void)" (?testWindow##YAXXZ) C:\Users\chansen\source\repos\ogl-renderer\out\build\x64-Debug\ogl-renderer C:\Users\chansen\source\repos\ogl-renderer\out\build\x64-Debug\ogl-test.cpp.obj 1
I am able to fix it by always including the .h and .cpp files in every location, but I want to figure this out the right way. I understand that I need to compile the source project into a library, then link that to the test executable, but I can't figure out how to do it correctly. I'm not even entirely certain how correct my CMakeList.txt files even are. I either guessed or copied on all these configurations.

Each CMakeLists.txt file defines a project to be compiled. And each project must contain all of its files, dependencies and build options.
So "test/CMakeLists.txt" must contain all relevant test source files. And "src/CMakeLists.txt" must contain all relevant src source files. This is why adding the .h and .cpp files fixed the error.
After each project is configured, linked libraries are defined with target_link_libraries().
For example in src the line target_link_libraries(ogl-renderer glfw) will link to glfw. And in test the line target_link_libraries(ogl-test ogl-renderer) will link to your src executable.
As long as the target_link_libraries() commands make sense, linking will be automated.
Some threads with more information:
What does enable_testing() do in cmake?
How to start working with GTest and CMake

Related

Why does CMake fail to link my static library?

I am trying to link a static library which I have compiled from the assimp source to my executable CMake project so that it does not have to compile assimp as often since it's such a large library.
I compiled it using CMake with the following commands:
git clone https://github.com/assimp/assimp/
cd assimp
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=OFF ../CMakeLists.txt
cmake --build .
My project structure looks like this:
.
OpenGL
├── OpenGL
│ ├── res
│ ├── src
│ | └── (my .cpp and .h files)
│ └── CMakeLists.txt
├── libraries
│ └── glfw, glew, imgui...
└── CMakeLists.txt
I now copy assimp/include and assimp/build/bin/Debug/config.h to OpenGL/libraries/assimp/include,
then assimp/build/lib/Debug to OpenGL/libraries/assimp/lib
My CMakeLists.txt(s) look like this:
cmake_minimum_required(VERSION 3.8)
project(OpenGL LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_FIND_DEBUG_MODE)
if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1")
endif()
if (CMAKE_BUILD_TYPE STREQUAL Debug)
add_definitions(-DDEBUG) # preprocessor macro
else()
if (MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") # if running on release mode remove the cmd prompt on windows
endif()
endif()
add_subdirectory(OpenGL)
add_subdirectory(libraries/glfw)
add_subdirectory(libraries/glew)
add_subdirectory(libraries/nativefiledialog)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/libraries/glfw/include
${CMAKE_CURRENT_SOURCE_DIR}/libraries/glew/include
OpenGL/src
libraries
libraries/stb
libraries/assimp/include
libraries/nativefiledialog/src/include
)
and
cmake_minimum_required(VERSION 3.8)
file(GLOB_RECURSE OpenGL_sources *.cpp ../libraries/imgui/*.cpp ../libraries/stb/*.cpp)
add_executable(${PROJECT_NAME} ${OpenGL_sources} "src/engine/input/Key.cpp" "src/engine/input/Buttons.h" "src/engine/input/Button.h" "src/engine/input/Button.cpp" "src/engine/input/MouseButton.h" "src/engine/input/MouseButton.cpp" "src/engine/rendering/objects/Model.h" "src/engine/rendering/objects/Model.cpp")
target_link_libraries(${PROJECT_NAME} PUBLIC
glfw
libglew_static
${CMAKE_SOURCE_DIR}/libraries/assimp/lib/assimp-vc143-mtd.lib
nativefiledialog
)
or
cmake_minimum_required(VERSION 3.8)
add_library( assimp STATIC IMPORTED )
set_property( TARGET assimp PROPERTY IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libraries/assimp/lib/assimp-vc143-mtd.lib)
file(GLOB_RECURSE OpenGL_sources *.cpp ../libraries/imgui/*.cpp ../libraries/stb/*.cpp)
add_executable(${PROJECT_NAME} ${OpenGL_sources} "src/engine/input/Key.cpp" "src/engine/input/Buttons.h" "src/engine/input/Button.h" "src/engine/input/Button.cpp" "src/engine/input/MouseButton.h" "src/engine/input/MouseButton.cpp" "src/engine/rendering/objects/Model.h" "src/engine/rendering/objects/Model.cpp")
target_link_libraries(${PROJECT_NAME} PUBLIC
glfw
libglew_static
assimp
nativefiledialog
)
(Both configurations give the same error)
Running cmake .. from OpenGL/build successfully completes which I think means that CMake was able to find the assimp library.
Now when I run cmake --build . I am greeted with the following error.
assimp-vc143-mtd.lib(AssbinLoader.obj) : error LNK2019: unresolved external symbol uncompress referenced in function "public: virtual void __cdecl Assimp::AssbinImporter::InternReadFile(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &,struct aiScene *,class Assimp::IOSystem *)" (?InternReadFile#AssbinImporter#Assimp##UEAAXAEBV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PEAUaiScene##PEAVIOSystem#2##Z) [F:\dev\cpp\OpenGL\build\OpenGL\OpenGL.vcxproj]
assimp-vc143-mtd.lib(Compression.obj) : error LNK2019: unresolved external symbol inflate referenced in function "public: unsigned __int64 __cdecl Assimp::Compression::decompress(void const *,unsigned __int64,class std::vector<char,class std::allocator<char> > &)" (?decompress#Compression#Assimp##QEAA_KPEBX_KAEAV?$vector#DV?$allocator#D#std###std###Z) [F:\dev\cpp\OpenGL\build\OpenGL\OpenGL.vcxproj]
etc...
I believe this means that the function bodies were not found which should be compiled within the .lib.
How do I successfully link the library?
These functions (uncompress and inflate) belong to the ZLIB library. Although assimp CMake links ZLIB in order to use it - which is enough to build assimp by itself, you have to link ZLIB directly to your executable as well in order to use it in your executable context.

When building a C++ static library what causes third party libraries hidden in source files to become end user dependencies?

Sorry if this question is a bit long winded. I know there is a lot of ground already covered here.
Essentially I get the below error when including my tst_engine.lib in another target.
lld-link: error: undefined symbol: SDL_Init
>>> referenced by C:\Users\n10255460\CLionProjects\2D_engine_and_game\tst_engine\TstGameEngine.cpp:22
>>> tst_engine.lib(TstGameEngine.cpp.obj):(public: __cdecl tst::TstGameEngine::TstGameEngine(bool *))
lld-link: error: undefined symbol: SDL_Quit
>>> referenced by C:\Users\n10255460\CLionProjects\2D_engine_and_game\tst_engine\TstGameEngine.cpp:43
>>> tst_engine.lib(TstGameEngine.cpp.obj):(public: __cdecl tst::TstGameEngine::~TstGameEngine(void))
ninja: build stopped: subcommand failed.
As It says above, SDL is used in a .cpp file not a .h file. On top of this, this error goes away when including SDL as a dependency in the target I'm attempting to compile with tst_engine as a library.
As far as I've previously understood things, if a third party library is in a source file it should never become a dependency for those who use the lib associated with the source file. Below are the two relevant cmake files:
executable that triggers the error:
cmake_minimum_required(VERSION 3.24)
project(game)
set(CMAKE_CXX_STANDARD 20)
include_directories(../tst_engine)
set(lib ${CMAKE_SOURCE_DIR}\\libraries\\lib)
include_directories(${CMAKE_SOURCE_DIR}\\libraries\\include)
add_executable(game main.cpp)
target_link_libraries(game
${CMAKE_SOURCE_DIR}\\cmake-build-debug\\tst_engine\\tst_engine.lib
)
library that compiles successfully:
cmake_minimum_required(VERSION 3.24)
project(tst_engine)
set(CMAKE_CXX_STANDARD 20)
set(lib ${CMAKE_SOURCE_DIR}\\libraries\\lib)
include_directories(${CMAKE_SOURCE_DIR}\\libraries\\include)
add_library(tst_engine STATIC TstGameEngine.h TstGameEngine.cpp)
target_link_libraries(tst_engine PRIVATE ${lib}\\SDL2main.lib ${lib}\\SDL2.lib ${lib}\\SDL2-static.lib)

How can I use a library from a git submodule with CMake?

I have a big project where I want to introduce a new library, both are mantained by me so full access to the code, CMakeLists etc
In Linux it is really easy, I just install the library with make install and then add it to my project with find_package(mynewlib REQUIRED). Then I use it as any other external library, target_link_libraries, add the headers and everything goes fine.
In Windows, I cannot make install, so I add it as a git submodule and use it like this:
find_package(MYLIB QUIET)
if(MYLIB_FOUND)
set_property(GLOBAL APPEND PROPERTY CUSTOM_STATUS MYLIB)
set_property(GLOBAL APPEND PROPERTY CUSTOM_STATUS_MYLIB
" MYLIB:" "version ${MYLIB_VERSION}")
else()
add_subdirectory(modules/mylib)
endif()
set(ALL_LIBS
Qt5::Core
Qt5::Widgets
MYLIB)
It doesn't work, linker starts complaining about unresolved symbols in different parts of the code, not related with the new introduced library. For example:
[build] Main.cpp.obj : error LNK2019: unresolved external symbol "int
__cdecl qInitResources_application(void)" (?qInitResources_application##YAHXZ) referenced in function main
[build] Main.cpp.obj : error LNK2001: unresolved external symbol
"public: static struct Version const Version::SOFTWARE_VERSION"
(?SOFTWARE_VERSION#Version##2U1#B) [build] targetapp\targetappcore.dll
: fatal error LNK1120: 52 unresolved externals
My question is, is my approach correct? MYLIB has a very big CMakeLists.txt I am currently analyzing in case it conflicts with the top level one, but if it was fine, is this how to do it?
I will consider using a package manager like vcpkg or conan in the future, but right now I would like to keep with git submodules.

Cannot find directory in context of main.cpp (Allegro with CMake)

I'm trying to create a library that links against allegro5 media library using CMake. And then, I want to use my library in an executable.
My directory structure is:
src
|----core
|------src
|------tests
|------CMakeLists.txt
|----main.cpp
|----CMakeLists.txt
The CMakeLists.txt file inside the core folder is:
set(MY_HEADERS #My header files here)
set(MY_SRC #My source files here)
add_library(MyLib ${MY_HEADERS} ${MY_SRC})
target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
## Allegro Lib
set(ALLEGRO_DIR "${PROJECT_SOURCE_DIR}/packages/allegro5/include")
if(WIN32)
file(GLOB ALLEGRO_LIB "${PROJECT_SOURCE_DIR}/packages/allegro5/windows/x86/lib/*.lib")
add_library(Allegro SHARED IMPORTED)
set_target_properties(Allegro PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${ALLEGRO_DIR}
IMPORTED_IMPLIB ${ALLEGRO_LIB}
)
target_link_libraries(MyLib PRIVATE Allegro)
file(GLOB ALLEGRO_DLL "${PROJECT_SOURCE_DIR}/packages/allegro5/windows/x86/bin/*.dll")
foreach(dll ${ALLEGRO_DLL})
add_custom_command(TARGET MyLib POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${dll} $<TARGET_FILE_DIR:MyLib>)
endforeach()
endif()
Now in my, top level CMakeLists.txt is:
cmake_minimum_required(VERSION 3.15)
project(MyProject)
enable_testing()
add_subdirectory(core)
add_executable(MyGame main.cpp)
target_link_libraries(MyGame MyLib)
In my IDE (Visual Studio) I see that the include statement for "allegro5/allegro.h" in my source files is inside core folder is giving me the warning "Cannot find directory allegro5 in search paths ... in context of ../src/main.cpp".
When I build the project I get the errors:
Cannot open source file 'allegro5/allegro'
Cannot open include file 'allegro5/allegro.h': No such file or directory.
This error only happens if I reference MyLib in main.cpp. (My main does not have any code, just a hello world statement):
// #include "MyLib.hpp"
int main(int argc, const char *argv[])
{
/*const auto engine = &MyLib::EngineManager::getInstance();
engine->start();
const auto display = &MyLib::DisplayManager::getInstance();
auto displayConfig = DisplayConfig();
displayConfig.fullscreen = false;
const auto displayId = display->createDisplay(displayConfig);*/
}
I guess this has something to do with the visibility I'm setting somewhere? I can't quite figure out how to fix. Appreciate your help, thanks!
EDIT:
When PRIVATE to PUBLIC, I get the following linker error:
LNK2019 unresolved external symbol __imp_al_install_system referenced in function "public: static void __cdecl mylib::EngineManager::start(void)" (?start#EngineManager#mylib##SAXXZ) C:\Users\xxx\src\out\build\x64-Debug\src C:\Users\xxx\src\out\build\x64-Debug\main.cpp.obj
The include directory for allegro5 is defined as ALLEGRO_DIR in your CMake:
${PROJECT_SOURCE_DIR}/packages/allegro5/include
Therefore, with the #include "allegro5/allegro.h", the full path to the header file would be appended:
${PROJECT_SOURCE_DIR}/packages/allegro5/include/allegro5/allegro.h
Be sure this path is correct and the file exists.
In addition, you set the INTERFACE include directories on the imported Allegro target, but you link it to MyLib using the PRIVATE keyword. This means the usage requirements of Allegro are not propagated to the MyGame. You should use PUBLIC for this link step instead if you want the INTERFACE_INCLUDE_DIRECTORIES of Allegro to also be transitively propagated to MyGame:
target_link_libraries(MyLib PUBLIC Allegro)
CMake has a good example demonstrating this in their documentation.

CMake generated MSVC project cannot find symbols even if lib files are correctly generated for their correlated dlls

I've been bashing my head against this problem for the last few days (my repo is linked down below). I want CMake to generate a MSVC solution for which the Engine project is linked to the Demo project. While the .lib and .dll files are correctly generated and added to the projects in MSVC, I still get unresolved linker symbol errors from the Demo project referencing symbols from the Engine project. The Engine.lib file is correctly added to Demo's dependencies, and so are the headers. I have no globals for which I'd need to use the generated exported headers. What's the issue here?
Root CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
project(P_SentryAll)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
include(GenerateExportHeader)
# glob source and header files
file(GLOB_RECURSE EngineSources SENTRY.Engine/*.cpp SENTRY.Engine/*.hpp)
file(GLOB_RECURSE CoreSources SENTRY.Core/*.cpp SENTRY.Core/*.hpp)
file(GLOB_RECURSE RenderSources SENTRY.Render/*.cpp SENTRY.Render/*.hpp)
file(GLOB_RECURSE DemoSources SENTRY.Demo/*.cpp SENTRY.Demo/*.hpp)
file(GLOB_RECURSE EngineHeaders SENTRY.Engine/*.hpp)
file(GLOB_RECURSE CoreHeaders SENTRY.Core/*.hpp)
add_subdirectory(SENTRY.Core)
add_subdirectory(SENTRY.Engine)
add_subdirectory(SENTRY.Render)
add_subdirectory(SENTRY.Demo)
Root/Sentry.Core/CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
# define project
project(P_SentryCore)
add_library(SentryCore SHARED ${CoreSources})
target_include_directories(SentryCore PUBLIC src/module)
generate_export_header(SentryCore)
install(TARGETS SentryCore DESTINATION lib)
install(FILES ${CoreHeaders} DESTINATION include/SentryCore)
Root/Sentry.Engine/CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
project(P_SentryEngine)
add_library(SentryEngine SHARED ${EngineSources})
target_link_libraries(SentryEngine PUBLIC SentryCore)
target_include_directories(SentryEngine PUBLIC src/engine)
generate_export_header(SentryEngine)
install(TARGETS SentryEngine DESTINATION lib)
install(FILES ${EngineHeaders} DESTINATION include/SentryEngine)
Root/Sentry.Demo/CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
# define project
project(P_SentryDemo)
add_executable(SentryDemo ${DemoSources})
target_link_libraries(SentryDemo PUBLIC SentryEngine)
include_directories(SENTRY.Engine/src/engine SENTRY.Core/src/module)
# packaging
install(TARGETS SentryDemo DESTINATION build)
Error:
1>------ Build started: Project: ZERO_CHECK, Configuration: Debug x64 ------
2>------ Build started: Project: SentryEngine, Configuration: Debug x64 ------
2>Engine.cpp
2>Auto build dll exports
2> Creating library C:/Users/main/Desktop/Projects/SENTRY/Build/SENTRY.Engine/Debug/SentryEngine.lib and object C:/Users/main/Desktop/Projects/SENTRY/Build/SENTRY.Engine/Debug/SentryEngine.exp
2>SentryEngine.vcxproj -> C:\Users\main\Desktop\Projects\SENTRY\Build\SENTRY.Engine\Debug\SentryEngine.dll
3>------ Build started: Project: SentryDemo, Configuration: Debug x64 ------
3>SENTRY.Demo.obj : error LNK2019: unresolved external symbol "public: void __cdecl Engine<__int64,struct std::ratio<1,1000000> >::Init(void)" (?Init#?$Engine#_JU?$ratio#$00$0PECEA##std####QEAAXXZ) referenced in function main
3>SENTRY.Demo.obj : error LNK2019: unresolved external symbol "public: void __cdecl Engine<__int64,struct std::ratio<1,1000000> >::Run(void)" (?Run#?$Engine#_JU?$ratio#$00$0PECEA##std####QEAAXXZ) referenced in function main
3>SENTRY.Demo.obj : error LNK2019: unresolved external symbol "public: void __cdecl Engine<__int64,struct std::ratio<1,1000000> >::RegisterModule(class Module<__int64,struct std::ratio<1,1000000> > *)" (?RegisterModule#?$Engine#_JU?$ratio#$00$0PECEA##std####QEAAXPEAV?$Module#_JU?$ratio#$00$0PECEA##std#####Z) referenced in function main
3>C:\Users\main\Desktop\Projects\SENTRY\Build\SENTRY.Demo\Debug\SentryDemo.exe : fatal error LNK1120: 3 unresolved externals
3>Done building project "SentryDemo.vcxproj" -- FAILED.
4>------ Skipped Build: Project: INSTALL, Configuration: Debug x64 ------
4>Project not selected to build for this solution configuration
========== Build: 2 succeeded, 1 failed, 2 up-to-date, 1 skipped ==========
Repo
The line in Root/Sentry.Demo/CMakeLists.txt:
include_directories(SENTRY.Engine/src/engine SENTRY.Core/src/module)
appears to be incorrect. It uses relative paths, so I don't believe these are valid paths in your project:
Root/Sentry.Demo/SENTRY.Engine/src/engine
Root/Sentry.Demo/SENTRY.Core/src/module
Prefer to use absolute paths wherever possible, through use of the CMAKE_SOURCE_DIR variable. This variable provides the path to the top-level source directory. So try something like this instead:
include_directories(
${CMAKE_SOURCE_DIR}/SENTRY.Engine/src/engine
${CMAKE_SOURCE_DIR}/SENTRY.Core/src/module
)
I took another look at your repo, and perhaps more importantly, you must have the full definition of Engine template functions in the header file, not the source file.
So move these function definitions to the header file, within your Engine class definition:
template<typename T_rep, typename T_ratio>
void Engine<T_rep, T_ratio>::Init()
{
for (auto& module_ : Modules)
{
module_->Init();
}
}
template<typename T_rep, typename T_ratio>
void Engine<T_rep, T_ratio>::Run()
{
RunUpdateLoop = true;
auto TPStart = std::chrono::steady_clock::now();
auto TPEnd = TPStart;
while (RunUpdateLoop)
{
auto deltaT = TPEnd - TPStart;
TPStart = std::chrono::steady_clock::now();
for (auto& module_ : Modules)
{
module_->Run((deltaT));
}
TPEnd = std::chrono::steady_clock::now();
}
}
template<typename T_rep, typename T_ratio>
void Engine<T_rep, T_ratio>::RegisterModule(Module<T_rep, T_ratio>* ToRegister)
{
Modules.push_back(ToRegister);
}
This should help get you on the right track.