CMake - Undefined reference encountered when attempting to build project

I am currently trying to use CMake on Windows to build a C++ project that includes 2 libraries, GLEW and SDL2, but upon doing so I get an undefined reference error that looks as follows:-

PS D:\Projects\C++Projects\game_engine\build>> cmg ..
-- The C compiler identification is GNU 7.1.0
-- The CXX compiler identification is GNU 7.1.0
-- Check for working C compiler: C:/Program Files/MinGW-w64/mingw64/bin/gcc.exe
-- Check for working C compiler: C:/Program Files/MinGW-w64/mingw64/bin/gcc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Program Files/MinGW-w64/mingw64/bin/g++.exe
-- Check for working CXX compiler: C:/Program Files/MinGW-w64/mingw64/bin/g++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: D:/Projects/C++Projects/game_engine/build
Scanning dependencies of target game_engine
[ 25%] Building CXX object CMakeFiles/game_engine.dir/src/MainGame.cpp.obj
[ 50%] Building CXX object CMakeFiles/game_engine.dir/src/Sprite.cpp.obj
[ 75%] Building CXX object CMakeFiles/game_engine.dir/src/main.cpp.obj
[100%] Linking CXX executable game_engine.exe
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x4b): undefined reference to `SDL_Quit'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x141): undefined reference to `SDL_Init'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x175): undefined reference to `SDL_CreateWindow'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x1e4): undefined reference to `SDL_GL_CreateContext'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x240): undefined reference to `__imp_glewInit'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x2a4): undefined reference to `SDL_GL_SetAttribute'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x2c1): undefined reference to `glClearColor'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x39e): undefined reference to `SDL_PollEvent'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x438): undefined reference to `glClearDepth'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x442): undefined reference to `glClear'
CMakeFiles\game_engine.dir/objects.a(MainGame.cpp.obj):MainGame.cpp:(.text+0x461): undefined reference to `SDL_GL_SwapWindow'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x30): undefined reference to `__imp___glewDeleteBuffers'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0xae): undefined reference to `__imp___glewGenBuffers'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x15d): undefined reference to `__imp___glewBindBuffer'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x175): undefined reference to `__imp___glewBufferData'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x198): undefined reference to `__imp___glewBindBuffer'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x1c1): undefined reference to `__imp___glewBindBuffer'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x1d9): undefined reference to `__imp___glewEnableVertexAttribArray' CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x1ea): undefined reference to `__imp___glewVertexAttribPointer'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x22b): undefined reference to `glDrawArrays'
CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x232): undefined reference to `__imp___glewDisableVertexAttribArray'

CMakeFiles\game_engine.dir/objects.a(Sprite.cpp.obj):Sprite.cpp:(.text+0x243): undefined reference to `__imp___glewBindBuffer'
C:/PROGRA~1/MINGW-~1/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.1.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x2e): undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles\game_engine.dir\build.make:118: game_engine.exe] Error 1
make[1]: *** [CMakeFiles\Makefile2:67: CMakeFiles/game_engine.dir/all] Error 2
make: *** [Makefile:83: all] Error 2

The project tree is:-

Root
|-build
|-include
   |-GL
   |-SDL2
   |-MainGame.h
   \-Sprite.h
|-lib
   |-glew32.lib
   |-glew32s.lib
   |-SDL2.lib
   |-SDL2main.lib
   \-SDL2test.lib
\-src
   |-main.cpp
   |-MainGame.cpp
   \-Sprite.cpp

My CMakeLists.txt file looks like this:-

cmake_minimum_required(VERSION 3.11.4)

project (game_engine)

# Bring header files into scope
include_directories(include)
include_directories(include/SDL2)
include_directories(include/GL)

# Retrieve all source files
file(GLOB SOURCES "src/*.cpp")

add_executable(game_engine ${SOURCES})
target_link_libraries(game_engine -L./lib)

At the moment, I believe the undefined references are a result of the compiler not finding the corresponding .cpp files in the SDL and GLEW libraries.

This leads me to believe that the last line in the CMakeLists.txt file is not successful at linking the .lib files for both libraries.

Any help at all would be greatly appreciated, and if the above errors are due to a poor project structure, which I suspect is the main issue here, I will gladly change it.

Thank you.

Edit(1):

I wish to add something I just noticed which is that cmake actually generates a makefile but then running make right after is what produces the undefined references error. Checking the makefile, I realise there is no linking of libraries at all which again confirms that target_link_libraries() was not able to link SDL and GLEW.

Here is the updated CMakeLists.txt file:

cmake_minimum_required(VERSION 3.11.4)       

# Project Name                               
project (game_engine)                        

# Retrieve all source files                  
set(SOURCES                                  
        ${CMAKE_SOURCE_DIR}/src/main.cpp     
        ${CMAKE_SOURCE_DIR}/src/MainGame.cpp 
        ${CMAKE_SOURCE_DIR}/src/Sprite.cpp)  

add_executable(game_engine ${SOURCES})       

target_include_directories(game_engine PUBLIC
        include                              
        include/SDL2                         
        include/GL)                          

target_link_libraries(game_engine            
        ${CMAKE_SOURCE_DIR}/lib/glew32.lib   
        ${CMAKE_SOURCE_DIR}/lib/glew32s.lib  
        ${CMAKE_SOURCE_DIR}/lib/SDL2.lib     
        ${CMAKE_SOURCE_DIR}/lib/SDL2main.lib 
        ${CMAKE_SOURCE_DIR}/lib/SDL2test.lib)

and the resulting makefile:

# CMAKE generated file: DO NOT EDIT!
# Generated by "MinGW Makefiles" Generator, CMake Version 3.11

# Default target executed when no arguments are given to make.
default_target: all

.PHONY : default_target

# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:


#=============================================================================
# Special targets provided by cmake.

# Disable implicit rules so canonical targets will work.
.SUFFIXES:


# Remove some rules from gmake that .SUFFIXES does not remove.
SUFFIXES =

.SUFFIXES: .hpux_make_needs_suffix_list


# Suppress display of executed commands.
$(VERBOSE).SILENT:


# A target that is always out of date.
cmake_force:

.PHONY : cmake_force

#=============================================================================
# Set environment variables for the build.

SHELL = cmd.exe

# The CMake executable.
CMAKE_COMMAND = D:\CMake\bin\cmake.exe

# The command to remove a file.
RM = D:\CMake\bin\cmake.exe -E remove -f

# Escaping for special characters.
EQUALS = =

# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = D:\Projects\C++Projects\game_engine

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = D:\Projects\C++Projects\game_engine\build

#=============================================================================
# Targets provided globally by CMake.

# Special rule for the target edit_cache
edit_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake cache editor..."
        D:\CMake\bin\cmake-gui.exe -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : edit_cache

# Special rule for the target edit_cache
edit_cache/fast: edit_cache

.PHONY : edit_cache/fast

# Special rule for the target rebuild_cache
rebuild_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."
        D:\CMake\bin\cmake.exe -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache

# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache

.PHONY : rebuild_cache/fast

# The main all target
all: cmake_check_build_system
        $(CMAKE_COMMAND) -E cmake_progress_start D:\Projects\C++Projects\game_engine\build\CMakeFiles D:\Projects\C++Projects\game_engine\build\CMakeFiles\progress.marks
        $(MAKE) -f CMakeFiles\Makefile2 all
        $(CMAKE_COMMAND) -E cmake_progress_start D:\Projects\C++Projects\game_engine\build\CMakeFiles 0
.PHONY : all

# The main clean target
clean:
        $(MAKE) -f CMakeFiles\Makefile2 clean
.PHONY : clean

# The main clean target
clean/fast: clean

.PHONY : clean/fast

# Prepare targets for installation.
preinstall: all
        $(MAKE) -f CMakeFiles\Makefile2 preinstall
.PHONY : preinstall

# Prepare targets for installation.
preinstall/fast:
        $(MAKE) -f CMakeFiles\Makefile2 preinstall
.PHONY : preinstall/fast

# clear depends
depend:
        $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles\Makefile.cmake 1
.PHONY : depend

#=============================================================================
# Target rules for targets named game_engine

# Build rule for target.
game_engine: cmake_check_build_system
        $(MAKE) -f CMakeFiles\Makefile2 game_engine
.PHONY : game_engine

# fast build rule for target.
game_engine/fast:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/build
.PHONY : game_engine/fast

src/MainGame.obj: src/MainGame.cpp.obj

.PHONY : src/MainGame.obj

# target to build an object file
src/MainGame.cpp.obj:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/MainGame.cpp.obj
.PHONY : src/MainGame.cpp.obj

src/MainGame.i: src/MainGame.cpp.i

.PHONY : src/MainGame.i

# target to preprocess a source file
src/MainGame.cpp.i:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/MainGame.cpp.i
.PHONY : src/MainGame.cpp.i

src/MainGame.s: src/MainGame.cpp.s

.PHONY : src/MainGame.s

# target to generate assembly for a file
src/MainGame.cpp.s:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/MainGame.cpp.s
.PHONY : src/MainGame.cpp.s

src/Sprite.obj: src/Sprite.cpp.obj

.PHONY : src/Sprite.obj

# target to build an object file
src/Sprite.cpp.obj:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/Sprite.cpp.obj
.PHONY : src/Sprite.cpp.obj

src/Sprite.i: src/Sprite.cpp.i

.PHONY : src/Sprite.i

# target to preprocess a source file
src/Sprite.cpp.i:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/Sprite.cpp.i
.PHONY : src/Sprite.cpp.i

src/Sprite.s: src/Sprite.cpp.s

.PHONY : src/Sprite.s

# target to generate assembly for a file
src/Sprite.cpp.s:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/Sprite.cpp.s
.PHONY : src/Sprite.cpp.s

src/main.obj: src/main.cpp.obj

.PHONY : src/main.obj

# target to build an object file
src/main.cpp.obj:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/main.cpp.obj
.PHONY : src/main.cpp.obj

src/main.i: src/main.cpp.i

.PHONY : src/main.i

# target to preprocess a source file
src/main.cpp.i:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/main.cpp.i .PHONY : src/main.cpp.i

src/main.s: src/main.cpp.s

.PHONY : src/main.s

# target to generate assembly for a file
src/main.cpp.s:
        $(MAKE) -f CMakeFiles\game_engine.dir\build.make CMakeFiles/game_engine.dir/src/main.cpp.s .PHONY : src/main.cpp.s

# Help Target
help:
        @echo The following are some of the valid targets for this Makefile:
        @echo ... all (the default if no target is provided)
        @echo ... clean
        @echo ... depend
        @echo ... game_engine
        @echo ... edit_cache
        @echo ... rebuild_cache
        @echo ... src/MainGame.obj
        @echo ... src/MainGame.i
        @echo ... src/MainGame.s
        @echo ... src/Sprite.obj
        @echo ... src/Sprite.i
        @echo ... src/Sprite.s
        @echo ... src/main.obj
        @echo ... src/main.i
        @echo ... src/main.s
.PHONY : help



#=============================================================================
# Special targets to cleanup operation of make.

# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
        $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles\Makefile.cmake 0
.PHONY : cmake_check_build_system

Edit(2): SOLVED

After identifying that the .lib files were not being linked correctly, I decided to re-download the GLEW and SDL2 libraries through their source packages. Both of them came with cmake as a build option and so through that I was able to get access to the static libraries (.a) + the import libraries (.dll.a).

I replaced the contents of lib with these libraries and updated my CMakeLists.txt file to include them as well as the OpenGL libraries:

cmake_minimum_required(VERSION 3.11.4)

# Project Name
project (game_engine)

# Retrieve all source files
set(SOURCES
        "${CMAKE_SOURCE_DIR}/src/main.cpp"
        "${CMAKE_SOURCE_DIR}/src/MainGame.cpp"
        "${CMAKE_SOURCE_DIR}/src/Sprite.cpp")

add_executable(game_engine ${SOURCES})

find_package(OpenGL REQUIRED)

target_include_directories(game_engine PUBLIC
        include
        include/SDL2
        include/GL
        ${OPENGL_INCLUDE_DIRS})

target_link_libraries(game_engine
        "${CMAKE_SOURCE_DIR}/lib/glew32.dll"
        "${CMAKE_SOURCE_DIR}/lib/libglew32.a"
        "${CMAKE_SOURCE_DIR}/lib/libglew32.dll.a"
        "${CMAKE_SOURCE_DIR}/lib/libSDL2.dll"
        "${CMAKE_SOURCE_DIR}/lib/libSDL2.dll.a"
        "${CMAKE_SOURCE_DIR}/lib/libSDL2main.a"
        "${CMAKE_SOURCE_DIR}/lib/libSDL2-static.a"
        "${OPENGL_LIBRARIES}")

add_custom_command(TARGET game_engine POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_SOURCE_DIR}/lib/glew32.dll"
        "${CMAKE_BINARY_DIR}")

add_custom_command(TARGET game_engine POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_SOURCE_DIR}/lib/libSDL2.dll"
        "${CMAKE_BINARY_DIR}")

This solved the main issue with the undefined references but there was still one left, which was an undefined reference to WinMain. To fix that I added #define SDL_MAIN_HANDLED to my main.cpp file before including "SDL.h".

2 answers

  • answered 2018-08-09 01:56 valeca

    Could you try to add

    link_directories(${CMAKE_SOURCE_DIR}/lib)

    before

    add_executable(game_engine ${SOURCES}) ?

  • answered 2018-08-09 02:04 J-Christophe

    The target_link_libraries expects either a CMake target or the path to a library.

    Try changing

    target_link_libraries(game_engine -L./lib)
    

    with

    target_link_libraries(game_engine
        ${CMAKE_SOURCE_DIR}/lib/libglew32.lib
        ${CMAKE_SOURCE_DIR}/lib/glew32s.lib
        ${CMAKE_SOURCE_DIR}/lib/SDL2.lib
        ${CMAKE_SOURCE_DIR}/lib/SDL2main.lib
        ${CMAKE_SOURCE_DIR}/lib/SDL2test.lib
        )
    

    Few other suggestions:

    • (1) prefer explicit listing of the source instead of using GLOB. See file(GLOB...) documentation for the rational
    • (2) instead of include_directories prefer using target_include_directories
    • (3) including binaries in your version control system is not recommended. Instead, you could download them directly using CMake. I can provide more details about possible approach
    • (4) ideally using calls find_package(SDL2 REQUIRED), find_package(Glew REQUIRED) and linking against the imported targets (or using variable like GLEW_LIBRARIES) would be more robust.