Reputation: 372
I recently switched the build system of my C++ project to CMake. I am trying to use the ExternalProject_Add function to download the required libraries(there are currently 3 of them, GLM and TINYOBJ are static and GLFW can be either static or dynamic) using git then link to them in my project. I want to be able to link these libraries (and possibly others) with minimal effort so that I can build on multiple platforms. Or if someone else comes in to work on the project, they won't have to worry too much about getting the correct libraries installed.
However, I keep getting these errors when building (on Windows 10 with MinGW):
[100%] Linking CXX executable app\OpenGLTest.exe
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xd): undefined reference to `FPSCounter::getElapsedTime()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x2b): undefined reference to `FPSCounter::reset()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x54): undefined reference to `FPSCounter::setLastTick()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x5e): undefined reference to `FPSCounter::addFrame()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x12d): undefined reference to `GLCamera::getCameraZoom()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x149): undefined reference to `GLCamera::setCameraZoom(float)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x1de): undefined reference to `GLCamera::getCameraPosition()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x747): undefined reference to `GLCamera::setCameraTarget(glm::tvec3<float, (glm::precision)0>)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x771): undefined reference to `GLCamera::setCameraPosition(glm::tvec3<float, (glm::precision)0>)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x968): undefined reference to `GLRenderer_Deferred::GLRenderer_Deferred()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xb98): undefined reference to `FPSCounter::FPSCounter()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xba2): undefined reference to `FPSCounter::FPSCounter()'
collect2.exe: error: ld returned 1 exit status
CMakeFiles\OpenGLTest.dir\build.make:98: recipe for target 'app/OpenGLTest.exe' failed
mingw32-make[2]: *** [app/OpenGLTest.exe] Error 1
CMakeFiles\Makefile2:66: recipe for target 'CMakeFiles/OpenGLTest.dir/all' failed
mingw32-make[1]: *** [CMakeFiles/OpenGLTest.dir/all] Error 2
Makefile:126: recipe for target 'all' failed
mingw32-make: *** [all] Error 2
My directory structure looks like this:
|-Project
|-BUILD (all the CMake output files are here)
| |-app (this is where the .exe is output to)
| |-downloads (dependencies are downloaded here)
| |-deps
|-OpenGL (this is the source directory)
|-deps-CMakeLists.txt
|-CMakeLists.txt
|-src
|-Main.cpp
|-**Other source files and headers of the "undefined reference" errors are in this directory**
|-RenderSystem
|-More Source files
Here is my CMakeLists.txt:
cmake_minimum_required(VERSION 3.2)
project(OpenGLTest)
set(CMAKE_CXX_FLAGS "-std=c++11")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(EX_PROJ_SOURCE_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Source)
set(EX_PROJ_BUILD_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Build)
# Include OpenGL
find_package(OpenGL REQUIRED)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
endif()
# Include GLEW
find_package(GLEW REQUIRED)
if (GLEW_FOUND)
include_directories(${GLEW_INCLUDE_DIRS})
endif()
set(GLFW_LIB_DIR ${EX_PROJ_BUILD_DIR}/GLFW_EX/src)
link_directories(${GLFW_LIB_DIR})
# Download and unpack gtest at configure time
configure_file(deps-CMakeLists.txt downloads/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
# Add gtest directly to our build
add_subdirectory(${EX_PROJ_SOURCE_DIR}/GLM_EX
${EX_PROJ_BUILD_DIR}/GLM_EX
EXCLUDE_FROM_ALL )
add_subdirectory(${EX_PROJ_SOURCE_DIR}/GLFW_EX
${EX_PROJ_BUILD_DIR}/GLFW_EX
EXCLUDE_FROM_ALL )
add_subdirectory(${EX_PROJ_SOURCE_DIR}/TINYOBJ_EX
${EX_PROJ_BUILD_DIR}/TINYOBJ_EX
EXCLUDE_FROM_ALL )
# Add the gtest include directory, since gtest
# doesn't add that dependency to its gtest target
include_directories(${EX_PROJ_SOURCE_DIR}/GLM_EX/glm
${EX_PROJ_SOURCE_DIR}/GLFW_EX/include
${EX_PROJ_SOURCE_DIR}/TINYOBJ)
# add the executable
add_executable(OpenGLTest src/Main.cpp)
target_link_libraries(OpenGLTest tinyobjloader glm glfw3 ${GLEW_LIBRARIES} ${OPENGL_LIBRARIES})
add_custom_command(TARGET OpenGLTest POST_BUILD # Adds a post-build event to MyTest
COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..."
"${GLFW_LIB_DIR}/glfw3.dll" # <--this is in-file
$<TARGET_FILE_DIR:OpenGLTest>) # <--this is out-file path
This is the deps-CMakeLists.txt file:
cmake_minimum_required(VERSION 3.2)
project(deps-download LANGUAGES NONE)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE "./deps")
# Include GLFW
ExternalProject_Add (
GLFW_EX
GIT_REPOSITORY "https://github.com/glfw/glfw.git"
GIT_TAG "master"
CMAKE_ARGS -DGLFW_BUILD_EXAMPLES=OFF
-DGLFW_BUILD_TESTS=OFF
-DGLFW_BUILD_DOCS=OFF
-DGLFW_INSTALL=OFF
-DBUILD_SHARED_LIBS=ON
UPDATE_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
# Include GLM
ExternalProject_Add (
GLM_EX
GIT_REPOSITORY "https://github.com/g-truc/glm.git"
GIT_TAG "master"
UPDATE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
# Include TINYOBJ
ExternalProject_Add (
TINYOBJ_EX
GIT_REPOSITORY "https://github.com/syoyo/tinyobjloader.git"
GIT_TAG "master"
UPDATE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
add_dependencies(GLFW_EX GLM_EX TINYOBJ_EX)
My "main" is located in Main.cpp in the "src" directory along with all the files referenced in the errors as "undefined reference". I've added the include directories for all the libraries(right after the ExternalProject_Add command) and attempted to link the dynamic library being built for GLFW but it still doesn't seem to work.
What am I missing to get this to build correctly? Any help would be appreciated.
UPDATE:
I've shuffled some things around and moved the ExternalProject_Add
commands to another file which are executed during the configure phase of my build, as suggested by Craig Scott. I've made sure that all the external libraries are linked. I even tested each library separately in a different test project to make sure the CMake files work.
All of the "undefined references" I'm getting are from files that I wrote, and are in my source tree. How/Why are they not being included?
Note: I have also tried to include the "src" directory but doesn't seem to do much of anything.
Upvotes: 0
Views: 2602
Reputation: 10137
While your CMakeLists.txt file does build the external projects, your OpenGLTest target doesn't link to them. Presumably, they are what provide the missing symbols your linker is complaining about. Simply adding those external projects as dependencies won't also add them to your OpenGLTest target.
To fix this problem, you need to add the external libraries to your target_link_libraries
command. Unfortunately, those libraries won't exist when you run CMake (well, not the first time anyway), so you have to manually work out the details of the libraries (but see further below for an alternative). It may be enough to know just the directory in which ExternalProject
will put them. It should be predictable for every build, so you can work out what it is by looking at one of your test builds. I believe you can then just add that path to the linker search path and list the base name of the library in the target_link_libraries
command (the base name meaning drop any leading "lib" on unix-like platforms as well as the file suffix). If that doesn't work, you will need to construct the full path to the library and add that to the target_link_libraries
command instead. This would require more work, especially if you want to build on multiple platforms (the set of CMake variables CMAKE_..._LIBRARY_PREFIX and CMAKE_..._LIBRARY_SUFFIX may be helpful here).
If manually specifying the library details bothers you and if the external projects also use CMake, there is a way to get ExternalProject
to download the sources for you but then use add_subdirectory
to bring them directly into your project. They would then have CMake targets you could use to specify on your target_link_libraries
command and would have the added benefit of always being built with consistent compiler/linker flags as the rest of your project. The technique is discussed with Google Test as the example here:
https://crascit.com/2015/07/25/cmake-gtest/
If you wanted to, you could modify that approach to do the whole build at CMake time and then use find_library
or similar, but that would make the CMake step potentially very costly and is not normally recommended.
Upvotes: 1
Reputation: 372
The main problem I was having was caused by not adding the source files to the final executable. I fixed the issue by adding a file(GLOB...
just before the add_executable
command like this:
# get all *.cpp files recursively
file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/*.cpp)
# add the executable
add_executable(OpenGLTest ${SRC_FILES})
I will probably move to a solution that involves a more explicit way of adding source files in the future since GLOB
s are not recommended.
Thanks to Craig Scott for your help.
Upvotes: 1