UE Build System:Target and Module

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
2
3
4
5
6
7
8
9
10
Example\GWorld\Source>tree /a /f
| GWorld.Target.cs
| GWorldEditor.Target.cs
|
\---GWorld
GWorld.Build.cs
GWorld.cpp
GWorld.h
GWorldGameModeBase.cpp
GWorldGameModeBase.h

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, what Type (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
2
3
4
5
6
7
8
9
10
11
12
class IModuleInterface
{
public:
virtual ~IModuleInterface();
virtual void StartupModule();
virtual void PreUnloadCallback();
virtual void PostLoadCallback();
virtual void ShutdownModule();
virtual bool SupportsDynamicReloading();
virtual bool SupportsAutomaticShutdown();
virtual bool IsGameModule(); const
};

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
2
3
4
5
6
7
8
public class GWorldTarget : TargetRules
{
public GWorldTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
ExtraModuleNames.AddRange( new string[] { "GWorld" } );
}
}

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 uses XXXX_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:

  1. Rename the existing GWorld.build.cs file to GWorldAAA.build.cs and replace all occurrences of GWorld in the file content with GWorldAAA;
  2. Rename all GWORLD_API in the project’s header files to GWORLDAAA_API, because the XXX_API export symbols depend on the ModuleName;

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Specifies how to link all the modules in this target
/// </summary>
[Serializable]
public enum TargetLinkType
{
/// <summary>
/// Use the default link type based on the current target type
/// </summary>
Default,

/// <summary>
/// Link all modules into a single binary
/// </summary>
Monolithic,

/// <summary>
/// Link modules into individual dynamic libraries
/// </summary>
Modular,
}
  • TargetLinkType.Default is the default value for LinkType; in this state, if the current Target‘s Type is Editor, it uses the Modular 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Backing storage for the LinkType property.
/// </summary>
[RequiresUniqueBuildEnvironment]
[CommandLine("-Monolithic", Value ="Monolithic")]
[CommandLine("-Modular", Value ="Modular")]
TargetLinkType LinkTypePrivate = TargetLinkType.Default;

/// <summary>
/// Specifies how to link modules in this target (monolithic or modular). This is currently protected for backwards compatibility. Call the GetLinkType() accessor
/// until support for the deprecated ShouldCompileMonolithic() override has been removed.
/// </summary>
public TargetLinkType LinkType
{
get
{
return (LinkTypePrivate != TargetLinkType.Default) ? LinkTypePrivate : ((Type == global::UnrealBuildTool.TargetType.Editor) ? TargetLinkType.Modular : TargetLinkType.Monolithic);
}
set
{
LinkTypePrivate = value;
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public enum UnrealTargetPlatform
{
/// <summary>
/// Unknown target platform
/// </summary>
Unknown,

/// <summary>
/// 32-bit Windows
/// </summary>
Win32,

/// <summary>
/// 64-bit Windows
/// </summary>
Win64,

/// <summary>
/// Mac
/// </summary>
Mac,

/// <summary>
/// XboxOne
/// </summary>
XboxOne,

/// <summary>
/// Playstation 4
/// </summary>
PS4,

/// <summary>
/// iOS
/// </summary>
IOS,

/// <summary>
/// Android
/// </summary>
Android,

/// <summary>
/// HTML5
/// </summary>
HTML5,

/// <summary>
/// Linux
/// </summary>
Linux,

/// <summary>
/// All desktop platforms
/// </summary>
AllDesktop,

/// <summary>
/// TVOS
/// </summary>
TVOS,

/// <summary>
/// Nintendo Switch
/// </summary>
Switch,

/// <summary>
/// NDA'd platform Quail
/// </summary>
Quail,

/// <summary>
/// Confidential platform
/// </summary>
Lumin,
}

In build.cs or target.cs, we can perform different actions based on the Platform.

For example:

1
2
3
4
if(Target.Platform != UnrealTargetPlatform.Win32 && Target.Platform != UnrealTargetPlatform.Win64)
{
PublicDefinitions.Add("HAVE_PTHREAD");
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/// <summary>
/// Platform groups
/// </summary>
public enum UnrealPlatformGroup
{
/// <summary>
/// this group is just to lump Win32 and Win64 into Windows directories, removing the special Windows logic in MakeListOfUnsupportedPlatforms
/// </summary>
Windows,

/// <summary>
/// Microsoft platforms
/// </summary>
Microsoft,

/// <summary>
/// Apple platforms
/// </summary>
Apple,

/// <summary>
/// making IOS a group allows TVOS to compile IOS code
/// </summary>
IOS,

/// <summary>
/// Unix platforms
/// </summary>
Unix,

/// <summary>
/// Android platforms
/// </summary>
Android,

/// <summary>
/// Sony platforms
/// </summary>
Sony,

/// <summary>
/// Target all desktop platforms (Win64, Mac, Linux) simultaneously
/// </summary>
AllDesktop,
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
// other code
UnrealTargetConfiguration CheckConfig = Target.Configuration;
switch (CheckConfig)
{
default:
case UnrealTargetConfiguration.Debug:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEBUG=1");
break;
case UnrealTargetConfiguration.DebugGame:
// Default to Development; can be overridden by individual modules.
case UnrealTargetConfiguration.Development:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEVELOPMENT=1");
break;
case UnrealTargetConfiguration.Shipping:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_SHIPPING=1");
break;
case UnrealTargetConfiguration.Test:
GlobalCompileEnvironment.Definitions.Add("UE_BUILD_TEST=1");
break;bUseDebugCRT
}
// other code
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void AppendCLArguments_CPP(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// other code...
if(CompileEnvironment.CppStandard >= CppStandardVersion.Latest)
{
Arguments.Add("/std:c++latest");
}
else if(CompileEnvironment.CppStandard >= CppStandardVersion.Cpp17)
{
Arguments.Add("/std:c++17");
}
else if(CompileEnvironment.CppStandard >= CppStandardVersion.Cpp14)
{
Arguments.Add("/std:c++14");
}
// other code...
}

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
2
3
4
5
6
7
8
9
10
11
12
public override void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
if (GlobalCompileEnvironment.bUseDebugCRT)
{
GlobalCompileEnvironment.Definitions.Add("_DEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does
}
else
{
GlobalCompileEnvironment.Definitions.Add("NDEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does
}
// other code
}

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
2
3
4
5
/// <summary>
/// Whether this target should be compiled as a DLL. Requires LinkType to be set to TargetLinkType.Monolithic.
/// </summary>
[RequiresUniqueBuildEnvironment]
public bool bShouldCompileAsDLL = false;

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:

Engine/World.h
1
2
/** Returns true if StreamingLevel is part of the levels being considered for update */
bool IsStreamingLevelBeingConsidered(ULevelStreaming* StreamingLevel) const { return StreamingLevelsToConsider.Contains(StreamingLevel); }

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
2
3
4
5
Undefined symbols for architecture x86_64:
"FStreamingLevelsToConsider::Contains(ULevelStreaming*) const", referenced from:
UWorldMgr::WorldCompositionTick(float) in Module.FGame.8_of_8.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

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:

target.cs
1
2
bOverrideBuildEnvironment = true;
bUseInlining = false;

In the UBT code, there is a check for its value, then actual compilation parameters will be added:

1
2
3
4
5
// 
if (!CompileEnvironment.bUseInlining)
{
Result += " -fno-inline-functions";
}

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
2
3
4
5
6
7
8
9
10
11
using UnrealBuildTool;
using System.IO;

public class GWorld : ModuleRules
{
public GWorld(ReadOnlyTargetRules ReadOnlyTargetRules) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// something
}
}

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 path PROJECT_NAME/Source/PROJECT_NAME.

EngineDirectory

  • string EngineDirectory: The absolute path of the engine directory Engine/ in the current environment.

PublicAdditionalLibraries

Adding static link library files (note the difference from PublicLibraryPaths), generally used for linking third-party libraries.

1
2
3
4
5
6
7
8
PublicAdditionalLibraries.AddRange(
new string[]
{
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotobuf.lib"),
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotobuf-lite.lib"),
Path.Combine(ThridPartyPath,"protobuf/lib/Win64/MD/Release","libprotoc.lib"),
}
);

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 and RuntimeDependencies.

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
2
3
// build.cs
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "myExternalLib.lib"));
PublicDelayLoadDLLs.Add("myExternalLib.dll");

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
2
3
4
5
6
FString AbsPath = FileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*MyLibPath);
FPlatformProcess::AddDllDirectory(AbsPath)
FPlatformProcess::PushDllDirectory(*AbsPath);
// direct call dll function
// or load dll handle
// void* DLLHandle3 = FPlatformProcess::GetDllHandle( L"myExternalLib.dll" );

PS: For DLL usage with import libraries, the process is as follows:

  1. Use PublicAdditionalLibraries to add the lib.
  2. Add the DLL to PublicDelayLoadDLLs.
  3. Use RuntimeDependencies to copy the DLL during packaging.
  4. If the copied location is not the exe path, AddDllDirectory and PushDllDirectory should be executed in StartupModule 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 unlike PublicIncludePaths, 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
2
3
4
5
PublicSystemIncludePaths.AddRange(
new string[] {
"TEST_LIB"
}
);

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:

  1. The system PATH;
  2. 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
#pragma comment(lib,"Lua.lib")

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 like FModuleManager::LoadModuleChecked<MODULE_TYPE>(TEXT("MODULE_NAME")) to start.
1
2
// e.g
FModuleManager::LoadModuleChecked< IAIModule >( "AIModule" );

PublicDependencyModuleNames

  • List<string> PublicDependencyModuleNames: Adds the source file dependencies of the executing Module, automatically adding the Public and Private source file inclusions of the dependent Modules.

PrivateDependencyModuleNames

  • List<string> PrivateDependencyModuleNames: Unlike PublicDependencyModuleNames, it implies that the source files in the dependent Module can only be used in Private.

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’s Private files, leading to No such file or directory errors when used in B’s Public files.
  • Conversely, if A is added through PublicDependencyModuleNames, its files are available in both B’s Public and Private 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:

  1. Add A module to C’s dependencies.
  2. 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:

  1. What is the difference between PublicDependencyModuleNames and PrivateDependencyModuleNames
  2. Explanation of Source Code folder structure?

bPrecompile and bUsePrecompiled

1
2
3
4
5
6
7
8
9
/// <summary>
/// Whether this module should be precompiled. Defaults to the bPrecompile flag from the target. Clear this flag to prevent a module being precompiled.
/// </summary>
public bool bPrecompile;

/// <summary>
/// Whether this module should use precompiled data. Always true for modules created from installed assemblies.
/// </summary>
public bool bUsePrecompiled;

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
2
3
4
5
6
7
8
9
public class A : ModuleRules
{
public A(ReadOnlyTargetRules Target) : base(Target)
{
// ...
bPrecompile=true;
// ...
}
}

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
2
3
4
5
6
7
8
9
public class A : ModuleRules
{
public A(ReadOnlyTargetRules Target) : base(Target)
{
// ...
bUsePrecompiled=true;
// ...
}
}

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
2
// build.cs
OptimizeCode = CodeOptimization.Never;

CodeOptimization supports several values, with the default being Default, enabling optimization:

  • Never
  • Default
  • InNonDebugBuilds
  • InShippingBuildsOnly

Related code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// UnrealBuildTool/Configutation/UEBuildModuleCPP.cs
public static bool ShouldEnableOptimization(ModuleRules.CodeOptimization Setting, UnrealTargetConfiguration Configuration, bool bIsEngineModule)
{
switch(Setting)
{
case ModuleRules.CodeOptimization.Never:
return false;
case ModuleRules.CodeOptimization.Default:
case ModuleRules.CodeOptimization.InNonDebugBuilds:
return (Configuration == UnrealTargetConfiguration.Debug)? false : (Configuration != UnrealTargetConfiguration.DebugGame || bIsEngineModule);
case ModuleRules.CodeOptimization.InShippingBuildsOnly:
return (Configuration == UnrealTargetConfiguration.Shipping);
default:
return true;
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
UnrealBuildTool\Platform\Windows\VCToolChain.cs

void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// other code ...

//
// Debug
//
if (CompileEnvironment.Configuration == CppConfiguration.Debug)
{
// Disable compiler optimization.
Arguments.Add("/Od");

// Favor code size (especially useful for embedded platforms).
Arguments.Add("/Os");

// Allow inline method expansion unless E&C support is requested
if (!CompileEnvironment.bSupportEditAndContinue && CompileEnvironment.bUseInlining)
{
Arguments.Add("/Ob2");
}

if ((CompileEnvironment.Platform == CppPlatform.Win32) ||
(CompileEnvironment.Platform == CppPlatform.Win64))
{
Arguments.Add("/RTCs");
}
}
//
// Development and LTCG
//
else
{
if(!CompileEnvironment.bOptimizeCode)
{
// Disable compiler optimization.
Arguments.Add("/Od");
}
else
{
// Maximum optimizations.
Arguments.Add("/Ox");

// Favor code speed.
Arguments.Add("/Ot");

// Coalesce duplicate strings
Arguments.Add("/GF");

// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (CompileEnvironment.bOmitFramePointers == false
&& ((CompileEnvironment.Platform == CppPlatform.Win32) ||
(CompileEnvironment.Platform == CppPlatform.Win64)))
{
Arguments.Add("/Oy-");
}
}

// Allow inline method expansion
Arguments.Add("/Ob2");

//
// LTCG
//
if (CompileEnvironment.bAllowLTCG)
{
// Enable link-time code generation.
Arguments.Add("/GL");
}
}

// other code...
}

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 GOOGLE_PROTOBUF_USE_UNALIGNED

If this macro is undefined, enabling bEnableUndefinedIdentifierWarnings will produce the C4688 error.

The related code is defined in UBT’s code:

1
2
3
4
5
6
7
8
9
10
11
12
// Source\Programs\UnrealBuildTool\Platform\Windows\VCToolChain.cs
if(WindowsPlatform.bUseVCCompilerArgs && CompileEnvironment.bEnableUndefinedIdentifierWarnings)
{
if (CompileEnvironment.bUndefinedIdentifierWarningsAsErrors)
{
Arguments.Add("/we4668");
}
else
{
Arguments.Add("/w44668");
}
}

bUseRTTI (bool)

UE4 disables RTTI by default, so when writing code with constructs like typeid, the following error will occur:

1
2
3
In file included from C:\UnrealProject_\Source\GWorld\Private\Modules\Flibs\FLibIniConfigHelper.cpp:10:
C:/UnrealProject_/Source/GWorld/Public/Modules/Flibs\FlibMateReflectionHelper.h(29,41): error: cannot use typeid with -fno-rtti
FString EnumTypeName = ANSI_TO_TCHAR(typeid(ENUM_TYPE).name());

The only solutions are to either remove the rtti related code or set bUseRTTI to true in the current Module‘s build.cs.

未完待续,如有谬误请不吝指正。

Scan the QR code on WeChat and follow me.

Title:UE Build System:Target and Module
Author:LIPENGZHA
Publish Date:2019/09/12 13:14
World Count:16k Words
Link:https://en.imzlp.com/posts/16643/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!