Typically, there are two ways for UE4 developers to obtain the UE4 engine:
- Install from Epic Games Launcher
- Clone the code from Github for local compilation
The version installed from Epic Games Launcher is the standard engine, which cannot be modified or recompiled, but you can choose platforms for installation support, debug symbols, etc. Cloning the code from Github and compiling it results in the source version of the engine, with some functionalities only available in the source version (like Standalone Application). However, if you modify the engine’s code in your project, it requires everyone to clone and compile the source again, which is very time-consuming. Additionally, the source version of the engine occupies a significant amount of disk space, reaching hundreds of gigabytes. When deploying the engine to multiple build machines, the time and space for compiling the engine become redundant. Therefore, it’s necessary to compile the engine on one machine, and the others can simply pull the compiled engine, achieving the same behavior as the installed version.
This article mainly introduces the workflow of BuildGraph and analyzes the engine’s default build script InstalledEngineBuild.xml
; how to use BuildGraph to configure, compile, and export a binary engine supporting Android/iOS packaging, and how to cut down and accelerate the overall build process.
UE provides the BuildGraph functionality, allowing us to build an installed version of the engine from source code similar to the one from Epic Games Launcher.
The parameters are as follows:
1 | RunUAT.bat BuildGraph -target="Make Installed Build Win64" -script=Engine/Build/InstalledEngineBuild.xml -set:WithMac=false -set:WithAndroid=false -set:WithIOS=false -set:WithTVOS=false -set:WithLinux=false -set:WithHTML5=false -set:WithSwitch=false -set:WithDDC=false -set:WithWin32=false -set:WithLumin=false -set:WithPS4=false -set:WithXboxOne=false -set:WithHoloLens=false -set:GameConfigurations=Development |
This executes the Make Installed Build Win64
from the InstalledEngineBuild.xml
script, realizing a one-stop process for engine compilation and binary export.
However, there are several issues: the default build can only package for Windows, not for Android/iOS.
How to build a binary engine supporting Android/iOS packaging will be discussed later, first let’s analyze the default InstalledEngineBuild.xml
script of UE.
Optimizing the BuildGraph Build Process
InstalledEngineBuild.xml
is the BuildGraph build script located in the engine’s Engine/Build/
directory, primarily implementing the functionality to export a binary engine via code, supporting many platforms. The official introduction and syntax of BuildGraph can be found here: BuildGraph.
We mainly use Make Installed Build Win64
, which is a node implementation.
The main process is as follows:
- Compile UBT and other build tools
- Compile NotForLicence tools
- Compile Editor
- Compile supported platforms (default Win64)
- Make Feature Packs
- Copy build results
If you want to modify the export path of the built binary engine, you can change the
BuiltDirectory
value inInstalledEngineBuild.xml
.
1 | <!-- Ouput directory for the build --> |
Moreover, upon analyzing Make Installed Build Win64
, many of the compilation processes are redundantly executed.
Take compiling UE4Game Win64
as an example:
1 | <Node Name="Compile UE4Game Win64" Requires="Compile UnrealHeaderTool Win64" Produces="#UE4Game Win64;#UE4Game Win64 Unstripped;#UE4Game Win64 Stripped;#UE4Game Win64 Unsigned;#UE4Game Win64 Signed"> |
It can be seen that the same target is compiled with different compile parameters twice:
- With parameters
-allmodules
,-nolink
, etc. - Without the above parameters
The -allmodules
parameter means that all modules in the engine must be recompiled, leading to a complete engine compilation. During the next execution, the entire engine will still be recompiled; the UBT contains a description of -allmodules
:
-allmodules
is defined in UBT’s TargetRules.cs
:
1 | /// <summary> |
It’s specifically for building the installed version of the engine, and I believe we can turn it off to use incremental compilation, otherwise, it will be too slow on CI.
Also, this script executes twice for each platform: once without the -nolink
parameter and once with it, which can be turned off based on your requirements. UE’s documentation describes it like this (Unreal Engine 4.14 Released!):
New: Added a -nolink command line option for Unreal Build Tool, which enables compiling a target without linking it. This is useful for automated build tests, such as non-unity compiles.
When using BuildGraph to build the engine, by default, the formula for calculating the compilation frequency of the engine is:
1 | UE4Editor DebugGame/Development 2 times |
If we use BuildGraph to build the installed engine via Make Installed Build Win64
(supporting Win/Android/IOS packaging), we need to compile at least 2+3*2=8
times, leading to compiling the engine code eight times! Each execution requires a full compilation of all modules, which takes a very long time.
Therefore, trimming is very necessary, as mentioned above:
- Remove the
-allmodules
parameter for incremental compilation - Remove the non-linking builds (depends on needs)
- Reduce the number of required build platforms
After trimming, the number of required compilations becomes:
- UE4Editor Development 1 time
- UE4Game Win64/Android/IOS/… Each platform
1*Configuration
times
This leads to 4 compilations, cutting it down by half from the default, and allowing for incremental compilation, greatly speeding up the process.
Building the Engine Supporting Android Packaging
To use Make Installed Build Win64
to build an engine that supports Android packaging, the -set:WithAndroid=true
parameter must be added when executing the BuildGraph script, as WithAndroid
is a parameter defined in InstalledEngineBuild.xml
and can be passed via the command line.
However, just specifying this on the command line is not enough. Before executing BuildGraph, the Android development environment needs to be properly configured, as BuildGraph will look for the paths of JDK/Android NDK/Android SDK and gradle
in the system PATH during the compilation process; you need to configure this in advance:
Note: Although the environment will be checked during compilation and gradle will be downloaded automatically, due to firewall issues, this may not succeed.
The Android environment should be added to the system environment variable:
Note: When using some CI tools, they can only see the environment variables of the user the service is running as, so it’s better to add them to the system environment variables to avoid additional issues.
By default, an engine that supports packaging for Android architectures armv7
and arm64
will be compiled. If you do not need a specific architecture, you can choose to comment it out in InstalledEngineBuild.xml
.
Configuring the Android development environment is straightforward; once done, you can directly export an engine that can package for Android:
Building the Engine Supporting iOS Packaging
As mentioned earlier, building a binary engine supporting Android packaging is relatively simple, but for the iOS platform, it’s more complex.
First, to build an engine supporting iOS packaging, a Mac must perform the remote build since BuildGraph needs to compile the iOS platform’s UE4Game, and cannot compile the iOS libraries on Windows, thus a Mac is mandatory.
- There is a Mac accessible in the local area network.
- Have a mobile provision.
- Generate an SSH Key.
As for “Why do we need to support iOS on Windows even with a Mac?” The primary reasons are:
- The Mac machine has limited capabilities. When packaging directly on Mac, both compilation and cooking operations occur there. By using remote builds, the Mac only handles code compilation, without needing to execute the cooking process. This reduces dependence on Mac resources and can enhance build efficiency.
- Using Windows uniformly for builds across all platforms (Win/Android/iOS).
First, SSH keys for the Mac need to be generated on Windows, as BuildGraph requires SSH keys to compile the engine for iOS. The generation method has been documented in my previous blog, and I will not elaborate again: UE4 Development Notes: Mac/iOS Section#Remote Build Configuration
Once the SSH key has been generated, the following configurations need to be modified in the engine’s Engine/Config/BaseEngine.ini
:
1 | [/Script/IOSRuntimeSettings.IOSRuntimeSettings] |
Note: The mobileprovision
and SigningCertificate
can be specified by name, such as:
1 | MobileProvision=com.XXXX.XXXX.fmgame_Development_SignProvision.mobileprovision |
Also, SSHPrivateKeyOverridePath
path doesn’t need to be specified, as RemoteMac.cs
will look for the SSH key in three automatically searched paths for the engine directory:
1 | if (ProjectFile != null) |
Place the SSH Key in any of these three directories using the following naming format:
1 | SSHKeys/IP_ADDR/USER_NAME/RemoteToolChainPrivate.key |
For example:
1 | SSHKeys/192.168.1.123/buildmachine/RemoteToolChainPrivate.key |
In engine versions 4.26 and later, the search path for SSH key has changed. See: ToolChain/RemoteMac.cs
1 | if (ProjectFile != null) |
For example:
1 | [PROJECT_DIR]\Restricted\NotForLicensees\Build\SSHKeys\192.168.31.55\lipengzha\RemoteToolChainPrivate.key |
Remote Build UBT Path Exception
Errors in RemoteMac.cs
during execution:
1 | /// <summary> |
In the above code, there’s no check during Combine
, thus Environment.SpecialFolder.ApplicationData
may not be found; therefore, this portion of code needs to be modified in UBT's
RemoteMac.cs
:
1 | private bool TryGetSshPrivateKey(out FileReference OutPrivateKey) |
The above operation will only resolve the path error when looking for the SSH key.
SSH Connection Error During Remote Build
After resolving this issue, there’s another:
1 | ****** [4/11] Compile UnrealHeaderTool Mac |
The error indicates that SSH key verification failed; however, it’s not the key itself that’s the problem (this is quite frustrating). The actual issue arises because of how the SSH connection command is coded in RemoteMac.cs
:
1 | class RemoteMac |
The command generated gives:
1 | \Engine\Extras\ThirdPartyNotUE\DeltaCopy\Binaries\ssh.exe -o BatchMode=yes -i \Engine\Build\NotForLicensees\SSHKeys\xx.xx.xx.xx\buildmachine\RemoteToolChainPrivate.key -p 22 buildmachine@xx.xx.xx.xx |
The critical issue lies in BatchMode=yes
; when we connect to a host for the first time via SSH, the following prompt appears:
1 | $ Engine\Extras\ThirdPartyNotUE\DeltaCopy\Binaries\ssh.exe -p 22 buildmachine@192.168.1.123 |
A prompt will ask you to verify the host (manual input of yes is required), but enabling BatchMode=yes
will disable all interactive prompts, causing the connection to fail upon any interaction! This is why we receive the Host key verification failed.
message despite having the correct key.
Knowing this issue, there are two ways to resolve it:
- Disable host verification for SSH connections.
- Before building, manually connect using the SSH command, allowing for interactive verification (initial verification required only once).
- It might also be due to overly permissive permissions on the key file on the client, so permission adjustments may be necessary.
To disable host verification during the SSH connection, you need to edit the Mac’s /etc/ssh/ssh_config
file to modify or add the following:
1 | StrictHostKeyChecking no |
Then restart the sshd
service:
1 | sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist |
Alternatively, modify the SSH connection command to add the following parameters:
1 | ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null user@192.168.1.123 |
The specific location for this is:
1 | CommonSshArguments = new List<string>(); |
Since rsync also uses the configured SSH information for file transfers during remote builds, the rsync command should also be modified:
1 | //++[RSTDIO][lipengzha] skip ssh new host auth |
The original rsync command:
1 | rsync --compress --verbose --rsh="./ssh -i '/cygdrive/D/Projects/Client/Build/NotForLicensees/SSHKeys/xx.xx.xx.xx/buildmachine/RemoteToolChainPrivate.key' -p 2222" --chmod=ugo=rwx --recursive --delete --delete-excluded --times --omit-dir-times --prune-empty-dirs --copy-links --rsync-path="mkdir -p /Users/buildmachine/UE4/Builds/lipengzha-PCb/D/Projects/Client && rsync" --filter="merge /cygdrive/D/Projects/Client/Build/Rsync/RsyncProject.txt" --filter="merge /cygdrive/D/Projects/Engine/Engine/Build/Rsync/RsyncProject.txt" --exclude='*' "/cygdrive/D/Projects/Client/" "buildmachine@xx.xx.xx.xx":'/Users/buildmachine/UE4/Builds/lipengzha-PCb/D/Projects/Client/' |
The modified command:
1 | rsync --compress --verbose --rsh="./ssh -i '/cygdrive/D/Projects/Client/Build/NotForLicensees/SSHKeys/xx.xx.xx.xx/buildmachine/RemoteToolChainPrivate.key' -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q -p 2222" --chmod=ugo=rwx --recursive --delete --delete-excluded --times --omit-dir-times --prune-empty-dirs --copy-links --rsync-path="mkdir -p /Users/buildmachine/UE4/Builds/lipengzha-PCb/D/Projects/Client && rsync" --filter="merge /cygdrive/D/Projects/Client/Build/Rsync/RsyncProject.txt" --filter="merge /cygdrive/D/Projects/Engine/Engine/Build/Rsync/RsyncProject.txt" --exclude='*' "/cygdrive/D/Projects/Client/" "buildmachine@xx.xx.xx.xx":'/Users/buildmachine/UE4/Builds/lipengzha-PCb/D/Projects/Client/' |
Error of Missing Import for Provision
Other compilation errors:
1 | ****** [6/11] Compile UE4Game IOS |
This is because the provision
was not configured when compiling the engine. You need to set it in Engine/Config/BaseEngine.ini
.
Here you can only specify the name of the provision
file, and the code in UEBuildIOS.cs
will look for it in the path C:\Users\lipengzha\AppData\Local\Apple Computer\MobileDevice\Provisioning Profiles
.
1 | protected IOSProvisioningData(IOSProjectSettings ProjectSettings, bool bIsTVOS, bool bForDistribtion) |
This operation can also be imported through the engine, not only requiring the import of the provision
but also the certificate (which can also be imported via iPhonePackager):
If not imported, the following errors will occur:
1 | ****** [7/12] Compile UE4Game IOS |
After resolving the above issues, the BuildGraph process can be completed normally, exporting the binary engine compiled based on the engine code. Once started, you can see that packaging for Windows/Android/iOS is possible.
## Error Troubleshooting
No certificate for team xxxx matching
This issue is likely due to the mismatch between the project’s configured certificate and the one in the system (or the same mobileProvision corresponds to multiple certificates). Follow the steps below, and a restart is required for changes to take effect.
If you receive the following error message:
1 | Code Signing Error: No certificate for team '9TV4ZYSS4J' matching 'iPhone Developer: Created via API (JDPXHYVWYZ)' found: Select a different signing certificate for CODE_SIGN_IDENTITY, a team that matches your selected certificate, or switch to automatic provisioning. |
Solution:
- Clear out excess mobileprovision files in
~/Library/MobileDevice/Provisioning\ Profiles
on Mac. - Clear out expired developer certificates in the Mac Keychain.
- Re-import mobileprovision and certificate.
Note: The imported mobileprovision file name must match the MobileProvision
specified in BaseEngine.ini
.
errSecInternalComponent error
- Xcode Command /usr/bin/codesign failed with exit code 1 : errSecInternalComponent
- iOS Remote Automatic Packaging Issues
- How to Fix iOS Application Code Signing Error?
- “Warning: unable to build chain to self-signed root for signer” warning in Xcode 9.2
Method 1
This occurs because calling /usr/bin/codesign
via SSH does not have permission to access the Keychain. You can use the following command to unlock it during an SSH session:
1 | security unlock-keychain -p password login.keychain |
In UE remote builds, you can first execute this command to unlock the Keychain in the current SSH environment, allowing subsequent signing to be executed properly. Modify the Engine\Build\BatchFiles\Mac\Build.sh
file to write the following content before calling UBT compile:
1 |
|
Since the Build.sh will be transmitted to Mac via RSync during compilation, you can observe the following log:
1 | [Remote] Executing build |
Thus, each compilation will unlock the Keychain, avoiding signing errors caused by lack of access to codesign when using SSH.
Note: Also check if the
SigningCertificate
value in BaseEngine.ini has been specified.
Method 2
If an Apple Developer Intermediate Certificate in the system has expired, this would also lead to the issue. The solution is to import a new Apple Developer Root Certificate:
- Apple Worldwide Developer Relations Intermediate Certificate Expiration
- Worldwide Developer Relations - G3 (Expiring 02/20/2030 00:00:00 UTC)
Delete the Apple Worldwide Developer Relations Certification Authority
from the Keychain - System, and then download the new one from the links above and re-import it.
Method 3
Check if the certificate was imported under Login in the Keychain, if so, delete it and re-import it under System.
Invalid trust settings
If the log contains the following error:
1 | Code Signing Error: Invalid trust settings. Restore system default trust settings for certificate "iPhone Developer: Created via API (JDPXHYVWYZ)" in order to sign code with it. |
This is because the certificate settings in the Mac Keychain have been changed to “Always Trust”. Change it back to “Use System Defaults”.
unable to build chain to self-signed root for signer
Check if the certificate is imported under Login or System categories in the Keychain. If it’s under Login, delete it and re-import it under System.
Copy Custom Directories
While building the binary engine, by default, it is consistent with the installation from Epic Launcher, but if you have modified the engine and want to package some new directories in.
Modify the InstalledEngineFilters.xml
file by adding directories under CopyEditorFilter
:
1 | <!-- Define Editor Filters --> |
Modify according to your needs.
Incremental Build of Binary Engine
To avoid recompiling Engine modules every time, which causes a lot of dependent modules to be recompiled, modify Engine.Build.cs
in the engine:
1 | PrivateIncludePathModuleNames.AddRange( |
Change to:
1 | PublicDependencyModuleNames.AddRange( |
Related discussion on UDN: Compile UE4Game Win64 particularly slow issue in Make Installed Build Win64
Conclusion
This article primarily introduces the workflow of BuildGraph, optimization for build speed, and support for packaging on Android/iOS.
It is important to note that the command used at the beginning of the article:
1 | RunUAT.bat BuildGraph -target="Make Installed Build Win64" -script=Engine/Build/InstalledEngineBuild.xml -set:WithMac=false -set:WithAndroid=false -set:WithIOS=false -set:WithTVOS=false -set:WithLinux=false -set:WithHTML5=false -set:WithSwitch=false -WithDDC=false -set:WithWin32=false -set:WithLumin=false -set:WithPS4=false -set:WithXboxOne=false -set:WithHoloLens=false -set:GameConfigurations=Development |
The GameConfigurations
was set to only Development
, so the engine compiled will only support packaging Development
. If you want to support Shipping packaging, you need to specify in BuildGraph
: -set:GameConfigurations=Development;Shipping
. However, this will increase the build time since both Development and Shipping need to be compiled separately, adding a time overhead for compiling the engine for all supported platforms, which is necessary, but can be selectively enabled during everyday development to save build time.