BuildGraph:构建支持多平台打包的二进制引擎

通常,UE4开发者获取UE4引擎的方式有两种:

  1. 从Epic Games Launcher安装
  2. 从Github上Clone代码本地编译

从EpicGamesLauncher安装的是公版引擎,不能修改代码重新编译,可以在根据选择安装支持的平台、调试符号等。
自己从Github上Clone代码进行编译的则是源码版引擎,有些功能只能在源码版中使用(比如Standalone Application),但是如果在项目中修改了引擎的代码,导致每个人都需要Clone一遍源码编译一遍引擎,这个过程十分耗时,而且源码版引擎的占用的磁盘空间十分巨大,达到上百G。在当需要把引擎部署到多台构建机时,编译引擎的时间和空间是冗余的,所以需要通过一台机器编译引擎,然后其他的机器只需要拉取编译后的引擎即可,实现与安装版引擎一样的行为。

本篇文章主要介绍BuildGraph的工作流程,以及对引擎默认构建脚本InstalledEngineBuild.xml的分析;如何使用BuildGraph从源码配置、编译并导出支持Android/IOS打包的二进制引擎、以及如何裁剪和加速整个的构建流程。

UE提供了BuildGraph功能使我们可以从源码中构建出和Epic Game Launcher相通的安装版引擎。

使用参数如下:

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

通过BuildGraph来执行InstalledEngineBuild.xml脚本中的Make Installed Build Win64实现,引擎编译、导出二进制的一条龙流程。

但是这样有几个问题:默认这样构建出来的只能够打包Windows,不能打包Android/IOS。

如何构建Android/IOS的二进制引擎,暂时先按下不表,首先先来分析一下UE默认的InstalledEngineBuild.xml脚本。

优化BuildGraph的构建流程

InstalledEngineBuild.xml是位于引擎的Engine/Build/目录下的BuildGraph的构建脚本,主要实现了通过代码导出二进制引擎的功能,支持了很多的平台。BuildGraph的官方介绍和语法:BuildGraph

我们主要使用Make Installed Build Win64,是它的一个Node实现。

主要流程如下:

  1. 编译UBT等构建工具
  2. 编译NotForLicence工具
  3. 编译Editor
  4. 编译支持的平台(默认Win64)
  5. Make Feature Packs
  6. 拷贝构建结果

而且,经过分析Make Installed Build Win64,发现它其中有很多重复执行的编译流程。

以编译UE4Game Win64为例:

1
2
3
4
5
6
7
8
<Node Name="Compile UE4Game Win64" Requires="Compile UnrealHeaderTool Win64" Produces="#UE4Game Win64;#UE4Game Win64 Unstripped;#UE4Game Win64 Stripped;#UE4Game Win64 Unsigned;#UE4Game Win64 Signed">
<ForEach Name="Target" Values="UE4Game;$(OptionalClientTarget);$(OptionalServerTarget)">
<ForEach Name="Configuration" Values="$(GameConfigurations)">
<Compile Target="$(Target)" Platform="Win64" Configuration="$(Configuration)" Tag="#UE4Game Win64" Arguments="-precompile -allmodules -nolink $(VSCompilerArg) $(TargetDebugInfoArg)"/>
<Compile Target="$(Target)" Platform="Win64" Configuration="$(Configuration)" Tag="#UE4Game Win64" Arguments="-precompile $(VSCompilerArg) $(TargetDebugInfoArg)" Clean="false"/>
</ForEach>
</ForEach>
</Node>

可以看到,针对同一个编译的Target,使用不同的编译参数执行了两次:

  1. 具有-allmodules-nolink等参数
  2. 不具有上述参数

而开启了-allmodules,则意味着引擎中所有的模块都要重新编译,是完整地编译引擎。当下次执行,还是要完整地编译整个引擎,UBT中具有对-allmodules的介绍:

-allmodules参数是定义在UBT的TargetRules.cs中的:

UnrealBuildTool/Configuration/TargetRules.cs
1
2
3
4
5
/// <summary>
/// Build all the modules that are valid for this target type. Used for CIS and making installed engine builds.
/// </summary>
[CommandLine("-AllModules")]
public bool bBuildAllModules = false;

专门用来构建安装版引擎的,我觉得可以关掉,使用增量编译的方式执行,不然在ci上每次执行都太慢了。

而且这个脚本中对每个平台都执行了两遍,一遍是不含-nolink参数的,一遍是包含-nolink参数的。
UE的文档中是这么介绍的(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.

当使用BuildGraph来构建引擎的时候,默认情况下对引擎的编译次数计算公式:

1
2
UE4Editor DebugGame/Development 2次
UE4Game Win64/Android/IOS/... 每个平台2*Configuration次(-allmodules -nolink)

如果我们使用BuildGraph通过Make Installed Build Win64来构建出安装版引擎(支持Win/Android/IOS打包),至少需要编译2+3*2=8,对引擎的代码要编译八次!而且每一次执行都要完整的编译所有的模块,耗时非常之长。

所以裁剪是非常有必要的,上面也已经提到了:

  1. 去掉-allmodules参数,增量编译
  2. 去掉-nolink的构建
  3. 减少需要构建的平台

裁剪之后需要需要编译的次数:

  1. UE4Editor Development 1次
  2. UE4Game Win64/Android/IOS/… 每个平台1*Configuration次

则需要编译4次,与默认减少一倍,并且可以增量编译,时间会快很多了。

构建引擎支持Android打包

想要使用Make Installed Build Win64构建出支持Android打包的引擎,需要在执行BuildGraph脚本时添加-set:WithAndroid=true,因为WithAndroidInstalledEngineBuild.xml中定义的参数,可以通过命令行传递。

不过,仅仅是命令行指定开启是不够的,在执行BuildGraph之前,需要安装好Android的开发环境,在BuildGraph在执行编译时会去系统PATH里查找JDK/Android NDK/Android SDK以及gradle的路径,需要自己进行预先配置:

注意:虽然在执行编译时会检测环境自动下载gradle,但是由于在墙内的原因,不一定会下载成功。

需要把Android的环境按照添加至系统的环境变量中:

注意:当使用一些ci工具的时候,ci只能查到服务启动的用户的环境变量,为避免额外的问题,最好添加至系统的环境变量。

默认情况下会编译出支持打包Android架构为armv7arm64的引擎,如果不需要其中的某个架构,可以在InstalledEngineBuild.xml里选择注释掉指定的平台。

Android比较简单,配置完Android的开发环境就可以直接导出可以打包Android的引擎:

构建引擎支持IOS打包

前面已经提到了,构建支持Android打包的二进制引擎,支持IOS平台要更复杂一些。

首先构建支持IOS打包的引擎必须要有一台Mac执行远程构建,因为BuildGraph需要编译IOS平台的UE4Game,在Win上无法编译IOS的库,所以必须要一台Mac。

  1. 局域网中有一台可访问的Mac
  2. 具有mobileprovision
  3. 生成SSH Key

至于“为什么有了Mac还需要在Win上支持IOS?”这个问题主要是因为:

  1. Mac机能受限,直接在Mac上打包是编译+Cook的操作都在Mac上,如果进行远程构建,则Mac只需要处理代码的编译,而不需要执行Cook的流程,降低对Mac机能的依赖,可以提高构建的效率。
  2. 统一地使用Win来构建出全平台包(Win/Android/IOS)

首先需要在Win上生成Mac的SSH Keys,BuildGraph编译引擎支持IOS也需要设置ssh key,生成的方法在我之前的博客中有记录,不再赘述:UE4 开发笔记:Mac/iOS 篇#配置远程构建

当生成了SSHkey之后,需要在引擎的Engine/Config/BaseEngine.ini中修改以下配置:

1
2
3
4
5
[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
RemoteServerName=
RSyncUsername=
SSHPrivateKeyOverridePath=
mobileprovision=

这里可以不指定SSHPrivateKeyOverridePath的路径,在RemoteMac.cs中,针对引擎目录有三个自动查找的路径:

1
2
3
4
5
6
if (ProjectFile != null)
{
Locations.Add(DirectoryReference.Combine(ProjectFile.Directory, "Build", "NotForLicensees"));
Locations.Add(DirectoryReference.Combine(ProjectFile.Directory, "Build", "NoRedist"));
Locations.Add(DirectoryReference.Combine(ProjectFile.Directory, "Build"));
}

把SSH Key按照这样的命名格式放到这三个目录下即可:

1
SSHKeys/IP_ADDR/USER_NAME/RemoteToolChainPrivate.key

如:

1
SSHKeys/192.168.1.123/buildmachine/RemoteToolChainPrivate.key

远程构建的UBT路径异常

在执行时RemoteMac.cs中报错的问题:

// RemoteMac.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
/// <summary>
/// Attempts to get the SSH private key from the standard locations
/// </summary>
/// <param name="OutPrivateKey">If successful, receives the location of the private key that was found</param>
/// <returns>True if a private key was found, false otherwise</returns>
private bool TryGetSshPrivateKey(out FileReference OutPrivateKey)
{
// Build a list of all the places to look for a private key
List<DirectoryReference> Locations = new List<DirectoryReference>();
Locations.Add(DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.ApplicationData), "Unreal Engine", "UnrealBuildTool"));
Locations.Add(DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool"));
// ...
}

上面的代码在Combine时没有做检测,Environment.SpecialFolder.ApplicationData这个路径是不一定能得到的,所以要修改UBT的RemoteMac.cs中的这部分代码,进行检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private bool TryGetSshPrivateKey(out FileReference OutPrivateKey)
{
// Build a list of all the places to look for a private key
List<DirectoryReference> Locations = new List<DirectoryReference>();
DirectoryReference ApplicationData = DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.ApplicationData);

DirectoryReference Personal = DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.Personal);
if( ApplicationData != null)
Locations.Add(DirectoryReference.Combine(ApplicationData, "Unreal Engine", "UnrealBuildTool"));
if(Personal != null)
Locations.Add(DirectoryReference.Combine(Personal, "Unreal Engine", "UnrealBuildTool"));

// ...
}

上一步的操作,只能解决找ssh key时的路径报错问题。

远程构建时SSH连接错误

解决了这个问题还有另外一个问题:

1
2
3
4
5
6
7
8
9
10
11
****** [4/11] Compile UnrealHeaderTool Mac

Reading local file list from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Update Version Files\Tag-Update Version Files.xml
Running: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Binaries\DotNET\UnrealBuildTool.exe UnrealHeaderTool Mac Development -NoUBTMakefiles -nobuilduht -precompile -allmodules -Manifest=C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Intermediate\Build\Manifest.xml -NoHotReload -log="C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Programs\AutomationTool\Saved\Logs\UBT-UnrealHeaderTool-Mac-Development.txt"
[Remote] Using remote server 'xx.xx.xx.xx' on port 2222 (user 'buildmachine')
[Remote] Using private key at C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Build\NotForLicensees\SSHKeys\xx.xx.xx.xx\buildmachine\RemoteToolChainPrivate.key
ERROR: Unable to determine home directory for remote user. SSH output:
Host key verification failed.
Took 0.6103776s to run UnrealBuildTool.exe, ExitCode=6
UnrealBuildTool failed. See log for more details. (C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Programs\AutomationTool\Saved\Logs\UBT-UnrealHeaderTool-Mac-Development.txt)

看到是ssh key的验证失败了,其实这个错误并不是Key的问题(这个问题非常坑)。
而是因为RemoteMac.cs中对ssh连接命令的代码里是这么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RemoteMac
{
// ...
/// <summary>
/// The authentication used for SSH (probably similar to RsyncAuthentication).
/// </summary>
[XmlConfigFile]
private string SshAuthentication = "-i '${CYGWIN_SSH_PRIVATE_KEY}'";
// ...
};

public RemoteMac(FileReference ProjectFile)
{
// ...
SshAuthentication = ExpandVariables(SshAuthentication);

// Build a list of arguments for SSH
CommonSshArguments = new List<string>();
CommonSshArguments.Add("-o BatchMode=yes");
CommonSshArguments.Add(SshAuthentication);
CommonSshArguments.Add(String.Format("-p {0}", ServerPort));
CommonSshArguments.Add(String.Format("\"{0}@{1}\"", UserName, ServerName));
// ...
}

这个代码生成的拼接的ssh命令:

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 [email protected]

问题的关键就是处在BatchMode=yes上,当我们第一次通过ssh连接一台主机,会下列提示:

1
2
3
4
5
6
$ Engine\Extras\ThirdPartyNotUE\DeltaCopy\Binaries\ssh.exe -p 22 [email protected] 192.168.1.123 
The authenticity of host '[ 192.168.1.123 ]:22 ([ 192.168.1.123 ]:22)' can't be established.
RSA key fingerprint is e0:8d:b9:7c:65:c7:9e:18:94:12:ed:ef:40:1a:15:47.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[ 192.168.1.123 ]:22' (RSA) to the list of known hosts.
Password:

会弹出提示让你验证这台主机(需要手动输入yes),但是开启了BatchMode=yes之后,会禁止所有的交互式提示,出现交互直接连接失败!这就是我们使用正确的Key,但是会提示Host key verification failed.的原因。
那么,知道了原因,解决这个问题的办法有两种:

  1. 关闭ssh的连接时验证
  2. 在进行构建之前,手动使用ssh命令连接主机,通过交互式的验证(只需要初始化验证一次)

未导入provision的错误

编译时的其他错误:

1
2
3
4
5
6
7
8
9
10
11
****** [6/11] Compile UE4Game IOS

Reading local file list from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Compile UnrealHeaderTool Mac\Tag-Compile UnrealHeaderTool Mac.xml
Reading local file list from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Update Version Files\Tag-Update Version Files.xml
Reading shared manifest from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Compile UnrealHeaderTool Mac\Manifest.xml
Running: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Binaries\DotNET\UnrealBuildTool.exe UE4Game IOS Development -NoUBTMakefiles -nobuilduht -precompile -allmodules -nolink -nodebuginfo -Manifest=C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Intermediate\Build\Manifest.xml -NoHotReload -log="C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Programs\AutomationTool\Saved\Logs\UBT-UE4Game-IOS-Development.txt"
[Remote] Using remote server 'xx.xx.xx.xxx' on port 22 (user 'buildmachine')
[Remote] Using private key at C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Build\NotForLicensees\SSHKeys\10.75.27.129\buildmachine\RemoteToolChainPrivate.key
[Remote] Using base directory '/Users/buildmachine/UE4/Builds/lipengzha-PC2'
ERROR: Unable to find mobile provision for UE4Game. See log for more information.
Took 4.0300959s to run UnrealBuildTool.exe, ExitCode=6

这是因为编译引擎时没有配置provision,需要在Engine/Config/BaseEngine.ini中设置。

在这里可以只指定provision文件的名字,在UEBuildIOS.cs中的代码会从C:\Users\lipengzha\AppData\Local\Apple Computer\MobileDevice\Provisioning Profiles路径下去查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected IOSProvisioningData(IOSProjectSettings ProjectSettings, bool bIsTVOS, bool bForDistribtion)
{
// ...
if(!string.IsNullOrEmpty(MobileProvision))
{
DirectoryReference MobileProvisionDir;
if(BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac)
{
MobileProvisionDir = DirectoryReference.Combine(new DirectoryReference(Environment.GetEnvironmentVariable("HOME")), "Library", "MobileDevice", "Provisioning Profiles");
}
else
{
MobileProvisionDir = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.LocalApplicationData), "Apple Computer", "MobileDevice", "Provisioning Profiles");
}

FileReference PossibleMobileProvisionFile = FileReference.Combine(MobileProvisionDir, MobileProvision);
if(FileReference.Exists(PossibleMobileProvisionFile))
{
MobileProvisionFile = PossibleMobileProvisionFile;
}
}
// ...
}

这部分操作也可以通过引擎来导入,不仅仅只需要导入provision还需要导入证书(也可以通过iPhonePackager来导入):

如果不导入会有下列错误:

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
****** [7/12] Compile UE4Game IOS

Reading local file list from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Compile UnrealHeaderTool Mac\Tag-Compile UnrealHeaderTool Mac.xml
Reading local file list from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Update Version Files\Tag-Update Version Files.xml
Reading shared manifest from C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Saved\BuildGraph\Compile UnrealHeaderTool Mac\Manifest.xml
Running: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Binaries\DotNET\UnrealBuildTool.exe UE4Game IOS Development -NoUBTMakefiles -nobuilduht -precompile -allmodules -nolink -nodebuginfo -Manifest=C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Intermediate\Build\Manifest.xml -NoHotReload -log="C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Programs\AutomationTool\Saved\Logs\UBT-UE4Game-IOS-Development.txt"
[Remote] Using remote server '192.168.1.123' on port 22 (user 'buildmachine')
[Remote] Using private key at C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Build\NotForLicensees\SSHKeys\192.168.1.123\buildmachine\RemoteToolChainPrivate.key
[Remote] Using base directory '/Users/buildmachine/UE4/Builds/lipengzha-PC3'
[Remote] Uploading C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Intermediate\Remote\UE4Game\IOS\Development\com.xxxxx.xxxx.xx_Development_SignProvision.mobileprovision
[Remote] Exporting certificate for C:\Users\lipengzha\AppData\Local\Apple Computer\MobileDevice\Provisioning Profiles\com.xxxxx.xxxx.xx_Development_SignProvision.mobileprovision...
Executing iPhonePackager ExportCertificate C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Source -provisionfile C:\Users\lipengzha\AppData\Local\Apple Computer\MobileDevice\Provisioning Profiles\com.xxxxx.xxxx.xx_Development_SignProvision.mobileprovision -outputcertificate C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Intermediate\Remote\UE4Game\IOS\Development\Certificate.p12
CWD: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Binaries\DotNET\IOS
Initial Dir: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Source
Env CWD: C:\BuildAgent\workspace\FGameEngine\Engine\Engine\Binaries\DotNET\IOS
BranchPath = lipengzha-PC3/C/BuildAgent/workspace/FGameEngine/Engine/Engine/Binaries --- GameBranchPath = lipengzha-PC3/C/BuildAgent/workspace/FGameEngine/Engine/Engine/Binaries

----------
Executing command 'ExportCertificate' '' ...
Looking for a certificate that matches the application identifier '9TV4ZYSS4J.com.xxxxx.xxxx.xx'
.. Provision entry SN '61B440405D86B84D' matched 0 installed certificate(s)
.. Failed to find a valid certificate that was in date
IPP ERROR: Failed to find a valid certificate

ERROR: IphonePackager failed.
Took 2.9281688s to run UnrealBuildTool.exe, ExitCode=6

上述的问题解决完毕之后就能够正常地把BuildGraph的流程执行完毕,导出了根据引擎代码编译出的二进制引擎,启动之后就可以看到能够打包Windows/Android/IOS了。

结语

本篇文章主要介绍了BuildGraph的工作流程、优化构建速度,以及支持Android/IOS的打包。

需要注意的是,文章开头使用的命令:

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

它的GameConfigurations只设置了Dvelopment,所以编译出来的引擎也只能打包Development,如果想要支持Shipping打包,就需要在BuildGraph中指定:-set:GameConfigurations=Development;Shipping,但是这样会增加构建的时间,因为Development和Shipping都是需要分别编译的,对执行支持的所有平台都增加了一次引擎编译的时间开销,当然是必要的,不过在日常的开发中,可以有选择性地开启,节省构建的时间。

全文完,若有不足之处请评论指正。

扫描二维码,分享此文章

本文标题:BuildGraph:构建支持多平台打包的二进制引擎
文章作者:查利鹏
发布时间:2020年11月08日 15时40分
本文字数:本文一共有4.3k字
原始链接:https://imzlp.me/posts/11956/
专栏链接:https://zhuanlan.zhihu.com/p/279625473
许可协议: CC BY-NC-SA 4.0
捐赠BTC:1CbUgUDkMdy6YRmjPJyq1hzfcpf2n36avm
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!