Using CMake and Make in Linux to build Google test for testing logic which use Unreal types

I’m trying to add tests to be able to test “backend” (for lack of better word) logic in our application. Problem is that these files are used in custom Unreal modules using Unreal types, such as TArray, TUniquePtr, and also UE_LOG

Our thought is to use Google Test for these files. We had some luck doing this with a custom compiled Unreal engine which allows us to run things as a console application. But we would rather want to just run the tests as free Google test executable.

To do this thing (which is obviously stupid and painful. But a very fun experiment.) I’m trying to follow this guide for doing something similar in Visual Studio on Windows

Unfortunately, doing the same in CMake on Linux is not working for me, probably cause I suck at CMake and build systems.

So far I managed to do this

Test.cpp

#define UE_BUILD_DEBUG 1
#define UE_BUILD_SHIPPING 0
#define WITH_EDITOR 0
#define WITH_ENGINE 0
#define WITH_UNREAL_DEVELOPER_TOOLS 0
#define WITH_PLUGIN_SUPPORT 0
#define IS_MONOLITHIC 1
#define IS_PROGRAM 1
#define WITH_SERVER_CODE 0

#define UBT_COMPILED_PLATFORM Linux
#define PLATFORM_LINUX 1

// WINDOWS BJAFS
// #define ENGINE_API __declspec( dllimport )
// #define CORE_API __declspec( dllimport )
// #define COREUOBJECT_API __declspec( dllimport )

#define _AMD64_

// UE5 EA declares a local variable named "X64" in two header files, which collides on the X64 def.
// https://github.com/EpicGames/UnrealEngine/pull/8989
#if defined(X64)
    #undef X64
    #define X64_UNDEFINED
#endif

#include "CoreMinimal.h"

#if defined(X64_UNDEFINED)
    #undef X64_UNDEFINED
    #define X64
#endif

#include "Modules/ModuleInterface.h"

// C:\Program Files\Epic Games\UE_5.0EA\Engine\Source\Runtime\CoreUObject\Public
#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"
#include "UObject/Class.h"
#include "Kismet/BlueprintFunctionLibrary.h"

int main(int, char**) { return 0; }

CMakeLists.txt

# trying based on https://gamedevtricks.com/post/going-commando/

cmake_minimum_required(VERSION 3.15)
project(Test)

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 14)

SET(CMAKE_CXX_FLAGS "-Wno-unused-parameter -Wno-unused-variable -Wno-unused-function")
SET(CMAKE_C_FLAGS "-Wno-unused-parameter -Wno-unused-variable -Wno-unused-function")

set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
set(UE_LINUX_LIBS ${PROJECT_ROOT}/../unreal-engine/Engine/Binaries/Linux/)
set(UE_ENGINE ${PROJECT_ROOT}/../unreal-engine/Engine/)
set(UE_ENGINE_SOURCE ${UE_ENGINE}/Source/)

include_directories(
    ${UE_ENGINE_SOURCE}/Runtime/Core/Public
    ${UE_ENGINE_SOURCE}/Runtime/CoreUObject/Public
    ${UE_ENGINE_SOURCE}/Runtime/TraceLog/Public
    ${UE_ENGINE_SOURCE}/Runtime/Engine/Classes
    ${UE_ENGINE}/Intermediate/Build/Linux/B4D820EA/UE4/Inc/Engine
)

link_directories(
    ${UE_LINUX_LIBS}
)

# Test sources
add_executable(${PROJECT_NAME}
    ${CMAKE_CURRENT_SOURCE_DIR}/Test.cpp
)

target_include_directories(${PROJECT_NAME}
PUBLIC
)

target_link_libraries(${PROJECT_NAME}
PUBLIC
    UE4Editor-Core
    UE4Editor-CoreUObject
)

toolchain.cmake

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
SET(UNREAL_DIR ${CMAKE_CURRENT_LIST_DIR}/../../unreal-engine/)
SET(UNREAL_LIBCXX_INC_PATH ${UNREAL_DIR}/Engine/Source/ThirdParty/Linux/LibCxx/include/c++/v1)
SET(UNREAL_LIBCXX_LIB_PATH ${UNREAL_DIR}/Engine/Source/ThirdParty/Linux/LibCxx/lib/Linux/x86_64-unknown-linux-gnu)

set(CMAKE_SYSROOT ${UNREAL_DIR}/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v16_clang-9.0.1-centos7/x86_64-unknown-linux-gnu)

SET(CMAKE_C_COMPILER ${CMAKE_SYSROOT}/bin/clang)
SET(CMAKE_CXX_COMPILER ${CMAKE_SYSROOT}/bin/clang++)
SET(CMAKE_LINKER ${CMAKE_SYSROOT}/bin/lld)

include_directories(${UNREAL_LIBCXX_INC_PATH})

# Where is the target environment
SET(CMAKE_FIND_ROOT_PATH
    ${CMAKE_SYSROOT}
    ${UNREAL_LIBCXX_LIB_PATH}
    )
set(CMAKE_LIBRARY_PATH
    ${UNREAL_LIBCXX_LIB_PATH}
    ${CMAKE_SYSROOT}/lib64
    ${CMAKE_SYSROOT}/usr/lib
    ${CMAKE_SYSROOT}/usr/lib64
    ${CMAKE_SYSROOT}/lib/gcc/x86_64-unknown-linux-gnu/4.8.5
    ${CMAKE_SYSROOT}/lib/gcc/x86_64-unknown-linux-gnu/4.8.5/../../../../lib64
    ${CMAKE_SYSROOT}/bin/../lib64
    ${CMAKE_SYSROOT}/lib/../lib64
    ${CMAKE_SYSROOT}/usr/lib/../lib64
    ${CMAKE_SYSROOT}/lib/gcc/x86_64-unknown-linux-gnu/4.8.5/../../..
    ${CMAKE_SYSROOT}/bin/../lib
    ${CMAKE_SYSROOT}/lib
)

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -frtti")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -nodefaultlibs -stdlib=libc++ -L${UNREAL_LIBCXX_LIB_PATH}")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -nodefaultlibs -stdlib=libc++ -L${UNREAL_LIBCXX_LIB_PATH}")
SET(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -nodefaultlibs -stdlib=libc++ -L${UNREAL_LIBCXX_LIB_PATH}")

# Search for programs only in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# Search for libraries and headers only in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

SET(CMAKE_UNREAL_COMPILATION 1)

link_libraries(
    c++
    c++abi
    c
    pthread
    gcc
    gcc_s
)

Building this with

 mkdir build ; cmake -S ./ -B ./build -DCMAKE_TOOLCHAIN_FILE=./toolchain.cmake ; cd build ; make

Gives the following error output

-- The C compiler identification is Clang 9.0.1
-- The CXX compiler identification is Clang 9.0.1
-- Check for working C compiler: /Test/../../unreal-engine//Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v16_clang-9.0.1-centos7/x86_64-unknown-linux-gnu/bin/clang
-- Check for working C compiler: /Test/../../unreal-engine//Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v16_clang-9.0.1-centos7/x86_64-unknown-linux-gnu/bin/clang -- 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: /Test/../../unreal-engine//Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v16_clang-9.0.1-centos7/x86_64-unknown-linux-gnu/bin/clang++
-- Check for working CXX compiler: /Test/../../unreal-engine//Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v16_clang-9.0.1-centos7/x86_64-unknown-linux-gnu/bin/clang++ -- 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: /Test/build
Scanning dependencies of target Test
[ 50%] Building CXX object CMakeFiles/Test.dir/Test.cpp.o
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:25:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:122:1: error: expected expression
public:
^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:16: error: variable has incomplete type 'class CORE_API'
class CORE_API FOutputDevice
               ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:59:10: error: cannot combine with previous 'type-name' declaration specifier
CORE_API bool LexTryParseString(EBuildConfiguration& OutConfiguration, const TCHAR* Configuration);
         ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:67:16: error: redefinition of 'TCHAR' as different kind of symbol
CORE_API const TCHAR* LexToString(EBuildConfiguration Configuration);
               ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/Platform.h:975:32: note: previous definition is here
typedef FPlatformTypes::TCHAR           TCHAR;
                                        ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:67:21: error: expected ';' after top level declarator
CORE_API const TCHAR* LexToString(EBuildConfiguration Configuration);
                    ^
                    ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:99:11: error: variable has incomplete type 'CORE_API'
        CORE_API EBuildConfiguration FromString( const FString& Configuration );
                 ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:99:30: error: expected ';' after top level declarator
        CORE_API EBuildConfiguration FromString( const FString& Configuration );
                                    ^
                                    ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:108:17: error: variable has incomplete type 'const CORE_API'
        CORE_API const TCHAR* ToString( EBuildConfiguration Configuration );
                       ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:108:22: error: expected ';' after top level declarator
        CORE_API const TCHAR* ToString( EBuildConfiguration Configuration );
                            ^
                            ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:116:11: error: variable has incomplete type 'CORE_API'
        CORE_API FText ToText( EBuildConfiguration Configuration );
                 ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:116:16: error: expected ';' after top level declarator
        CORE_API FText ToText( EBuildConfiguration Configuration );
                      ^
                      ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:150:10: error: cannot combine with previous 'type-name' declaration specifier
CORE_API bool LexTryParseString(EBuildTargetType& OutType, const TCHAR* Text);
         ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:158:16: error: redefinition of 'TCHAR' as different kind of symbol
CORE_API const TCHAR* LexToString(EBuildTargetType Type);
               ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/Platform.h:975:32: note: previous definition is here
typedef FPlatformTypes::TCHAR           TCHAR;
                                        ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:158:21: error: expected ';' after top level declarator
CORE_API const TCHAR* LexToString(EBuildTargetType Type);
                    ^
                    ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:178:11: error: variable has incomplete type 'CORE_API'
        CORE_API EBuildTargetType FromString( const FString& Target );
                 ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:178:27: error: expected ';' after top level declarator
        CORE_API EBuildTargetType FromString( const FString& Target );
                                 ^
                                 ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:181:17: error: variable has incomplete type 'const CORE_API'
        CORE_API const TCHAR* ToString(EBuildTargetType Target);
                       ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/Misc/OutputDevice.h:120:7: note: forward declaration of 'CORE_API'
class CORE_API FOutputDevice
      ^
In file included from /Test/Test.cpp:33:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/CoreMinimal.h:27:
In file included from /Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h:5:
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:181:22: error: expected ';' after top level declarator
        CORE_API const TCHAR* ToString(EBuildTargetType Target);
                            ^
                            ;
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h:287:16: error: redefinition of 'TCHAR' as different kind of symbol
CORE_API const TCHAR* LexToString(EAppReturnType::Type Value);
               ^
/Test/../../unreal-engine/Engine/Source/Runtime/Core/Public/HAL/Platform.h:975:32: note: previous definition is here
typedef FPlatformTypes::TCHAR           TCHAR;
                                        ^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
make[2]: *** [CMakeFiles/Test.dir/build.make:63: CMakeFiles/Test.dir/Test.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:76: CMakeFiles/Test.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Is there anyone out there skilled enough to explain to me what I’m missing? Or should I just give up?

And I dont’t want to hear “Just use the Unreal testing framework” or “Use the unreal build system” or “You should not do that.”. I already know that this is a pretty stupid idea. But I want to figure out how to do it anyway. :crazy_face:

So we got a bit further now.

when calling mkdir build ; cmake -S ./ -B ./build ; cd build ; make, this actually builds.

Test.cpp

#define UE_BUILD_DEBUG 0
#define UE_BUILD_SHIPPING 1
#define WITH_EDITOR 0
#define WITH_ENGINE 0
#define WITH_UNREAL_DEVELOPER_TOOLS 0
#define WITH_PLUGIN_SUPPORT 0
#define IS_MONOLITHIC 1
#define IS_PROGRAM 1
#define WITH_SERVER_CODE 0

#define UBT_COMPILED_PLATFORM Linux
#define PLATFORM_LINUX 1

#define _AMD64_

#define ENGINE_API
#define CORE_API
#define COREUOBJECT_API

#include "CoreMinimal.h"

#include "Modules/ModuleInterface.h"

#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"
#include "UObject/Class.h"
#include "Kismet/BlueprintFunctionLibrary.h"

int main(int, char**)
{
    return 0;
}

CMakeLists.txt

# trying based on https://gamedevtricks.com/post/going-commando/

cmake_minimum_required(VERSION 3.15)
project(Test)

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 14)

SET(CMAKE_CXX_FLAGS "-Wno-error=undef -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -fPIC")

set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
set(UE_LINUX_LIBS ${PROJECT_ROOT}/../unreal-engine/Engine/Binaries/Linux/)
set(UE_ENGINE ${PROJECT_ROOT}/../unreal-engine/Engine/)
set(UE_ENGINE_SOURCE ${UE_ENGINE}/Source/)

include_directories(
    ${UE_ENGINE_SOURCE}/Runtime/Core/Public
    ${UE_ENGINE_SOURCE}/Runtime/CoreUObject/Public
    ${UE_ENGINE_SOURCE}/Runtime/TraceLog/Public
    ${UE_ENGINE_SOURCE}/Runtime/Engine/Classes
    ${UE_ENGINE}/Intermediate/Build/Linux/B4D820EA/UE4/Inc/Engine
)

link_directories(
    ${UE_LINUX_LIBS}
)

# Test sources
add_executable(${PROJECT_NAME}
    ${CMAKE_CURRENT_SOURCE_DIR}/Test.cpp
)

target_include_directories(${PROJECT_NAME}
PUBLIC
)

target_link_libraries(${PROJECT_NAME}
PUBLIC
UE4Editor-Core
UE4Editor-CoreUObject
)

Output:

-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- 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: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- 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: /home/petermattsson/Testing/Test/build
Scanning dependencies of target Test
[ 50%] Building CXX object CMakeFiles/Test.dir/Test.cpp.o
[100%] Linking CXX executable Test
[100%] Built target Test

Now we just have to figure out how to add Google tests to this.

Small PoC of using Google Test (gtest) with CMake and Unreal Engine in Linux without loading Unreal game engine.