Module is the basic element that constitutes Unreal. Each Module encapsulates and implements a set of functions, which can be used by other Modules. The entire Unreal Engine is driven by the combination of various Modules, and even the game project that we create is a separate Module.
So how does UE create and build these Modules? This is the main purpose of writing this article, to study Unreal’s build system and the various properties they support (Target and Module).
It is recommended to read my previous article before looking at this one: Build flow of the Unreal Engine4 project, which mainly outlines the build process of UE; this article is merely one part of the UE build system.
Anyone familiar with UE projects knows that when a C++ game project is created using UE, a Source
folder is created under the project path, which by default contains the following files:
1 | Example\GWorld\Source>tree /a /f |
Among them, *.Target.cs
and *.Build.cs
are the actual controllers of the Unreal build system. UBT determines the entire compilation environment by scanning these two files, and they are the focus of this article. Their responsibilities differ:
*.Target.cs
controls the external compilation environment of the generated executable, known as the Target. For example, whatType
(Game/Client/Server/Editor/Program) is generated, whether to enable RTTI (bForceEnableRTTI
), how CRT is linked (bUseStaticCRT
), etc.*.Build.cs
controls the Module compilation process, managing dependencies, file inclusions, linking, macro definitions, and other related operations for the associated Module.*.Build.cs
informs the UE build system that it is a Module and specifies what actions to take during compilation.
In short: Anything related to the external compilation environment falls under *.target.cs
and anything related to the Module itself falls under *.build.cs
.
As a side note, the actual execution logic of the Module is defined in GWorld.h
and GWorld.cpp
, using IMPLEMENT_MODULE
. All Modules in UE inherit from IModuleInterface
and have the following interface:
1 | class IModuleInterface |
The IModuleInterface
drives the startup and shutdown of Modules, but generally, Game Module
does not use this to control the game flow. Detailed content on this can be found in my previous article: UE4 Modules: Load and Startup
Target
Each project based on Unreal has a Tergat.cs
, which contains a class definition inheriting from TargetRules
; it is also recommended to associate this with a Module definition of the same name (though not strictly necessary) to avoid compilation errors for undefined Modules. Its implication is to compile the specified Module into the Target:
1 | UnrealBuildTool : error : Could not find definition for module 'GWorld' (referenced via GWorld.Target.cs) |
The name of the Module associated with Target
can be specified through ExtraModuleNames
:
1 | public class GWorldTarget : TargetRules |
The above specifies GWorld
, so when UBT parses it, it will look for the definition of the GWorld
Module in the GWorld.build.cs
file, which contains the GWorld
class definition. If it’s not found, the aforementioned Module undefined error will occur.
Note: The Module associated with
Target
is not just a simple specified name; all code that usesXXXX_API
is related to the Module’s name.
If I make the following change: ExtraModuleNames.AddRange( new string[] { "GWorldAAA" } );
, then the changes required in all source files in the project are:
- Rename the existing
GWorld.build.cs
file toGWorldAAA.build.cs
and replace all occurrences ofGWorld
in the file content withGWorldAAA
; - Rename all
GWORLD_API
in the project’s header files toGWORLDAAA_API
, because theXXX_API
export symbols depend on theModuleName
;
This is indeed a considerable amount of work, so it is advisable to keep the name specified in ExtraModuleNames
the same as the Game Module
.
Through the above, we understand how Target.cs
is linked to Build.cs
. In fact, the Game
/Server
/Client
/Editor
Targets can share the same Module; just set their ExtraModuleNames
to be the same (if you want to write them separately for each Target type, that’s fine too).
The code for TargetRules
can be found in UnrealBuildTools/Configuration/ModuleRules (and ReadOnlyTargetRules
is defined there as well), where you can see the default values for supported parameters; UE’s documentation describing properties for Target
: Targets.
However, the official documentation from UE only includes comments from the code, and some descriptions are a bit hard to grasp after reading; I will analyze the meanings of some TargetRule
properties later on, so let’s leave that for now.
Type(TargetType)
The property Type
in TargetRules
is of type TargetType
, defined in TargetRules.cs
, which specifies what program the project is to build.
- Game - A standalone game which requires cooked data to run.
- Client - Same as Game, but does not include any server code. Useful for networked games.
- Server - Same as Game, but does not include any client code. Useful for dedicated servers in networked games.
- Editor - A target which extends the Unreal Editor.
- Program - A standalone utility program built on top of the Unreal Engine.
LinkType(TargetLinkType)
The LinkType
in TargetRules
is of type TargetLinkType
, defined in TargetRules.cs
, specifying the linking type of the project.
TargetLinkType
has three enumeration values:
1 | /// <summary> |
TargetLinkType.Default
is the default value forLinkType
; in this state, if the currentTarget
‘sType
isEditor
, it uses theModular
type, linking all modules as dynamic link libraries.TargetLinkType.Modular
: Links Modules as dynamic link libraries.TargetLinkType.Monolithic
: Links all modules into a single file (static link).
You can modify LinkType
.
1 | /// <summary> |
Name(string)
The name of the Target is a read-only property, coming from the project name.
Platform(UnrealTargetPlatform)
The Platform
is of type UnrealTargetPlatform
, which is an enumeration defined in UnrealBuildTool\Configuration\UEBuildTarget.cs
.
It records the current Target’s platform information, such as Win32/Win64, and currently, the supported platforms in version UE_4.22
are:
1 | public enum UnrealTargetPlatform |
In build.cs
or target.cs
, we can perform different actions based on the Platform.
For example:
1 | if(Target.Platform != UnrealTargetPlatform.Win32 && Target.Platform != UnrealTargetPlatform.Win64) |
IsInPlatformGroup
This is a function bool IsInPlatformGroup(UnrealPlatformGroup Group)
, defined in TargetRules.cs
, which is used to determine whether the current Platform
is part of a specific group.
The parameter required is of the enumeration type UnrealTargetformGroup
, which is defined in UEBuildTarget.cs
:
1 | /// <summary> |
Configuration(UnrealTargetConfiguration)
The current compilation configuration is of type UnrealTargetConfiguration
, defined in UEBuildTarget.cs
and constructed from the Configuration
in VS, such as:
- Development
- Shipping
- DebugGame
- Debug
- Test
- Unknow
It is through this setting that UBT adds the following macros in the compilation environment:
1 | public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment) |
Architecture(string)
The architecture information of the platform on which it runs: x86
/arm
, etc.
CppStandard(CppStandardVersion)
Used to specify the C++ standard version used when compiling the project (available only in newer engine versions (4.23)).CppStandardVersion
:
Latast
Cpp17
Cpp14
This option essentially adds /std:c++xxx
to the VS compilation options.
1 | void AppendCLArguments_CPP(CppCompileEnvironment CompileEnvironment, List<string> Arguments) |
bUseDebugCRT(bool)
Used to control whether the output Runtime Library
type is MT
or MD
; it also controls the addition of _DEBUG
and NODEBUG
macros:
1 | public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment) |
ProjectDefinitions(List)
Macro definitions added for the current project, available throughout the project.
GlobalDefinitions(List)
Macro definitions that can be used throughout the entire Target.
bShouldCompileAsDLL(bool)
Compilers the Target as a DLL, requiring LinkType
to be Monolithic
when true
.
1 | /// <summary> |
AdditionalCompilerArguments(String)
Arguments passed to the compiler.
AdditionalLinkerArguments(String)
Arguments passed to the linker.
bUsesSlate(bool)
Controls whether Slate
related image resources are packaged into the pak during packaging.
bUseInlining(bool)
Whether to enable inline optimization; inlining essentially reduces function call overhead by expanding functions.
However, there may be some issues, such as in the engine’s Engine
module, the FStreamingLevelsToConsider function, which does not export the symbol ENGINE_API
.
In the Engine/World.h
, the function IsStreamingLevelBeingConsidered
calls it as defined in the header file:
1 | /** Returns true if StreamingLevel is part of the levels being considered for update */ |
The function defined in the header file is implicitly inlined by the compiler, and if the call to World->IsStreamingLevelBeingConsidered
is inlined in other modules, it leads to a linking error:
1 | Undefined symbols for architecture x86_64: |
Because inlining expands to directly execute at the calling site, with the FStreamingLevelsToConsider
symbol not being exported, it leads to a linking error.
To resolve such issues, you can either modify the engine to export the symbol or disable inlining in the target.cs:
1 | bOverrideBuildEnvironment = true; |
In the UBT code, there is a check for its value, then actual compilation parameters will be added:
1 | // |
Module
Similar to Target
, each Unreal Module
has a dedicated ModuleName.Build.cs
where a dedicated ModuleName
class is defined, inheriting from ModuleRules
. The operations we perform during the module build are controlled through it.
Note: Whether it’s a Game Module or a Plugin Module, as long as it’s a project-dependent Module, it will receive the current
Target
information during compilation.
The code for ModuleRules
can be found in UnrealBuildTools/Configuration/ModuleRules, and you can also look at the default values for supported properties there; UE’s official documentation describing Modules
: Modules only contains comments in the code without actual examples. I will analyze some common properties in Build.cs in practical projects.
In *.Build.cs
, you can retrieve property information from *.Target.cs
through the ReadOnlyTargetRules Target
parameter.
1 | using UnrealBuildTool; |
Through the Target
object, you can manage operations on the Module based on different platforms (Platform), architectures (Architecture), and other options (such as defining different macros, including different ThirdParty libraries, linking different libraries, etc.).
ModuleDirectory
string ModuleDirectory
: The absolute path to the project source pathPROJECT_NAME/Source/PROJECT_NAME
.
EngineDirectory
string EngineDirectory
: The absolute path of the engine directoryEngine/
in the current environment.
PublicAdditionalLibraries
Adding static link library files (note the difference from PublicLibraryPaths
), generally used for linking third-party libraries.
1 | PublicAdditionalLibraries.AddRange( |
For detailed information, you can read: Linking Static Libraries Using The Build System
It can also be used for DLL import libraries, in combination with
PublicDelayLoadDLLs
andRuntimeDependencies
.
PublicAdditionalShadowFiles
When executing remote compilation, specify the files that need to be copied to the remote server for the current module to ensure a successful link.
For instance, when remotely packaging for iOS platforms, you need to add the statically linked libraries that the current module depends on (e.g., the Game module depends on a plugin’s External
module).
RuntimeDependencies
list<RuntimeDependency> RuntimeDependencies
: Files (.so
/.dll
, etc.) that the Module depends on at runtime, which will be copied to the storage directory during packaging.
When packaging for Windows, the files will be copied directly to the corresponding directory, but on Android, the files will be placed in the Apk package’s main.obb.webp.
PublicDelayLoadDLLs
List<string> PublicDelayLoadDLLs
: A list of DLLs to be loaded lazily, usually used for third-party libraries.
1 | // build.cs |
The meaning is that the DLLs in the list are not loaded immediately upon program startup; instead, they are loaded once their symbols are needed for the first time. This allows them to be loaded in the StartupModule
method as needed, without placing the DLL next to the executable.
1 | FString AbsPath = FileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*MyLibPath); |
PS: For DLL usage with import libraries, the process is as follows:
- Use
PublicAdditionalLibraries
to add the lib. - Add the DLL to
PublicDelayLoadDLLs
. - Use
RuntimeDependencies
to copy the DLL during packaging. - If the copied location is not the exe path,
AddDllDirectory
andPushDllDirectory
should be executed inStartupModule
to add the DLL’s path there.PublicDelayLoadDLLs
only needs the DLL name (xxxx.dll), no path is required.
I tested using DLL + import library and placed the DLL in a non-exe directory in GoogleInstantPreview
. Here are some examples: ue4-plugin-GoogleInstanceIns.7z
PublicDefinitions
List<string> PublicDefinitions
: Adds public macro definitions to the current Module, equivalent to adding a preprocessor macro in traditional VS project settings.
Once analyzed by UBT, it produces a Definitions.PROJECT_NAME.h
header file, which defines various macros.
1 | Intermediate\Build\Win64\UE4Editor\Development\ReflectionExample\Definitions.ReflectionExample.h |
PublicSystemIncludePaths
List<string> PublicSystemIncludePaths
: The documentation states it is for adding system include paths, but unlikePublicIncludePaths
, it skips header file resolution checks (however, my testing shows that code included this way still detects the following errors (UE_4.20)):
1 | error : Expected mpack-platform.h to be first header included. |
Note: If the paths are not specified, the default IncludePath is
Engine/Source
.
For example:
1 | PublicSystemIncludePaths.AddRange( |
This indicates the path is:
1 | D:\UnrealEngine\Epic\UE_4.21\Engine\Source\TEST_LIB |
The default path for any specified *IncludePaths
in *.build.cs
is Engine/Source
.
PrivateRuntimeLibraryPaths
List<string> PrivateRuntimeLibraryPaths
: The search path for runtime libraries, e.g.,.so
or.dll
.
PublicRuntimeLibraryPaths
List<string> PublicRuntimeLibraryPaths
: The search path for runtime libraries, e.g.,.so
or.dll
.
Because the default search paths for dynamic link libraries are:
- The system PATH;
- The current directory of the executable;
If our dynamic link library resides elsewhere, runtime errors will occur. We can add the library directories using PublicRuntimeLibraryPaths
or PrivateRuntimeLibraryPaths
.
PublicLibraryPaths
To add the path for linking library files, such as using in source code:
1 |
You can use PublicLibraryPaths
to add the dependent Lib.
DynamicallyLoadedModuleNames
List<string> DynamicallyLoadedModuleNames
: Adds Modules that need to be dynamically loaded at runtime, using functions likeFModuleManager::LoadModuleChecked<MODULE_TYPE>(TEXT("MODULE_NAME"))
to start.
1 | // e.g |
PublicDependencyModuleNames
List<string> PublicDependencyModuleNames
: Adds the source file dependencies of the executing Module, automatically adding thePublic
andPrivate
source file inclusions of the dependent Modules.
PrivateDependencyModuleNames
List<string> PrivateDependencyModuleNames
: UnlikePublicDependencyModuleNames
, it implies that the source files in the dependent Module can only be used inPrivate
.
For instance, if there is a Module A and another Module B, both structured with UE's Module/Public
and Module/Private
file systems.
- If B depends on A and the dependency was added using
PrivateDependencyModuleNames
, then A’s source files can only be used in B’sPrivate
files, leading toNo such file or directory
errors when used in B’sPublic
files. - Conversely, if A is added through
PublicDependencyModuleNames
, its files are available in both B’sPublic
andPrivate
directories.
In addition to the difference, it also affects modules dependent on B. When a module C depends on B, it can only access classes exposed from B’s PublicDependencyModule.
For example, C depends on B, and B depends on A; if C wants to access classes in A, there are two options:
- Add A module to C’s dependencies.
- Ensure B’s dependency on A is added to PublicDependencyModuleNames, allowing C indirect access to A.
Testing reveals that for Game Modules (PROJECT_NAME/Source/PROJECT_NAME.target.cs
), it doesn’t significantly matter whether the dependent Module is added through PublicDependencyModuleNames
or PrivateDependencyModuleNames
; headers from the Module can still be used in Game Modules’ Public
directory under certain conditions (unless the dependent module is set with bUsePrecompiled
, which alters accessibility).
Relevant discussions:
- What is the difference between PublicDependencyModuleNames and PrivateDependencyModuleNames
- Explanation of Source Code folder structure?
bPrecompile and bUsePrecompiled
1 | /// <summary> |
These two properties need to be used together.
Consider this scenario:
If we want to provide a Module A to others for use, but do not wish to disclose the complete code, what should we do?
In the traditional C++ domain, we would say: compile the code into a DLL and only release the headers and DLL to the users.
Exactly! The bPrecompile
and bUsePrecompiled
serve a similar purpose.
When compiling Module A, we add to its *.build.cs
:
1 | public class A : ModuleRules |
After completing the compilation, delete the Source/Private
folder in Module A (ensure you back it up first), and remove the Intermediate
folder while keeping the Binaries
. Finally, open Module A’s A.build.cs
, delete bPrecompile=true;
, and add:
1 | public class A : ModuleRules |
With this, our objective is achieved: the implementation code (Private
) is not released, and pre-compiled binaries are provided, but this does not allow for static linking. If it’s only exposed for Blueprint usage, that can work, but using its symbols in other Modules will lead to undefined symbol errors.
OptimizeCode(CodeOptimization)
This property is used to control whether to enable code optimization for the current module. When debugging in VS, you may sometimes see “Variable has been optimized away and is therefore unavailable,” which is due to optimization.
It can be used to disable optimization:
1 | // build.cs |
CodeOptimization
supports several values, with the default being Default
, enabling optimization:
- Never
- Default
- InNonDebugBuilds
- InShippingBuildsOnly
Related code:
1 | // UnrealBuildTool/Configutation/UEBuildModuleCPP.cs |
This function is called in UEBuildModuleCPP.cs
‘s CreateModuleCompileEnvironment
, and the result is assigned to CppCompileEnvironment.bOptimizeCode
, which is subsequently used in VCToolChain.cs
:
1 | UnrealBuildTool\Platform\Windows\VCToolChain.cs |
As seen, optimization is disabled by default in the Debug
environment. In non-Debug
configurations, whether to enable optimization is determined by the value of CompileEnvironment.bOptimizeCode
.
Debugging results:
When using the default (with OptimizeCode = CodeOptimization.Default;
):
When code optimization is disabled (OptimizeCode = CodeOptimization.Never;
):
It is recommended to use OptimizeCode = CodeOptimization.InShippingBuildsOnly;
.
Note: This option is the same as the setting for
Properties
-Configuration
-C/C++
-Optimization
-Optimization
in a regular C++ project in VS.
### bEnableUndefinedIdentifierWarnings (bool)
Whether to enable warnings for the use of undefined identifiers in preprocessed code #if
.
1 |
If this macro is undefined, enabling bEnableUndefinedIdentifierWarnings
will produce the C4688
error.
The related code is defined in UBT’s code:
1 | // Source\Programs\UnrealBuildTool\Platform\Windows\VCToolChain.cs |
bUseRTTI (bool)
UE4 disables RTTI by default, so when writing code with constructs like typeid
, the following error will occur:
1 | In file included from C:\UnrealProject_\Source\GWorld\Private\Modules\Flibs\FLibIniConfigHelper.cpp:10: |
The only solutions are to either remove the rtti
related code or set bUseRTTI
to true
in the current Module
‘s build.cs
.