Contents

UE4 编译系统

https://yinpengd.github.io/2019/08/13/%E4%BB%8E%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90%E8%99%9A%E5%B9%BB%E5%BC%95%E6%93%8E%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/

https://zhuanlan.zhihu.com/p/157965866 https://imzlp.me/posts/6362/

UE4 编译

UE4的游戏代码工程,从工程的属性可以得知,Build/Clean/Rebuild都是依赖于UBT(Build.bat、Clean.bat和Rebuild.bat)。生成sln文件也依赖于UBT。

1
2
3
4
5
6
# Build
Engine\Build\BatchFiles\Build.bat
# ReBuild
Engine\Build\BatchFiles\Rebuild.bat
# Clean
Engine\Build\BatchFiles\Clean.bat

Generate

这里面的所有配置其实都是我们调用Generate的时候自动为我们添加进去的,我们需要首先看我们Generate 的时候会发生什么,之后是分析这些脚本主要是做了什么。

Generate Visual Studio project files

我们Generate的时候,依旧是调用UE4的脚本GenerateProjectFiles.bat

1
2
3
4
5
call "%~dp0GetMSBuildPath.bat"
call "%~dp0FixDependencyFiles.bat"
call "%~dp0FindPlatformExtensionSources.bat"
..\Binaries\DotNET\UnrealBuildTool.exe -ProjectFiles %*

  1. GetMSBuildPath.bat:首先我们查看系统中是否存在MSBuild。MSBuild全称(Microsoft Build Engine),是用于构建应用程序的平台。在使用VS做开发,那么一定时时刻刻在使用它。因为是它在背后为你管理生成你的项目文件。当新建一个项目时,注意下项目文件夹中的*.*proj文件就是为MSBuild提供的,这是个文本文件,基于XML格式,里面包含有项目所包含的文件,生成配置,输出配置等信息。

  2. FixDependencyFiles.bat:检测编辑器使用的Icon。

  3. md ..\Intermediate\Build >nul 2>nul将对应目录清空

Intermediate文件主要初始化了一些马上要用的空文件夹,和在/Build/BuileRules文件夹下的UBT扫描模块时生成的地址(.txt),动态库(.dll),pdb(调试信息文件);还在ProjectFiles文件夹中生成了一些UE4和项目的工程配置文件。

  1. dir /s /b Programs\UnrealBuildTool\*.cs >..\Intermediate\Build\UnrealBuildToolFiles.txt 找到目录下所有以后缀名为.cs的文件添加到UnrealBuildToolFiles.txt中

  2. if exist ..\..\Platforms dir /s /b ..\..\Platforms\*.cs >> ..\Intermediate\Build\UnrealBuildToolFiles.txt//如果存在platforms目录,则将该目录下面的文件也都添加到unrealbuildToolFiles.txt里面

  3. fc /b ..\Intermediate\Build\UnrealBuildToolFiles.txt ..\Intermediate\Build\UnrealBuildToolPrevFiles.txt >nul 2>nul//将unrealBuildToolFiles.txt中的内容拷贝到UnrealBuildToolPrevFiles.txt中

  4. %MSBUILD_EXE% /nologo /verbosity:quiet Programs\UnrealBuildTool\UnrealBuildTool.csproj /property:Configuration=Development /property:Platform=AnyCPU /target:Clean清空

  5. %MSBUILD_EXE% /nologo /verbosity:quiet Programs\UnrealBuildTool\UnrealBuildTool.csproj /property:Configuration=Development /property:Platform=AnyCPU /target:Build编译UBT,我们将得到其可执行文件。

  6. 调用编译后的UBT的exe, 调用到基本的UnrealBuildTool.cs中的Main函数。

Build

build 依旧是调用脚本。这才是本文的重中之重。 Engine\Build\BatchFiles\Build.bat 其主要顺序为

  1. build.bat 中调用 UBT
  2. UBT 执行 target.cs 和所有 Module 的 build.cs 中的逻辑
  3. UBT 调用 UHT (根据 UE 的宏标记生成代码)
  4. UHT 生成完毕后,UBT 调用编译器
  5. 预处理
  6. 编译
  7. 链接

在我们介绍UBT功能时,首先我们要知道怎么才能调试UBT。我们首先将UBT设置为启动项目 2020-11-05-19-52-50.png 之后我们把build 的参数列表翻出来,把下面参数填到里面Command line arguments

1
..\..\Build\BatchFiles\Build.bat -Target="YY29Editor Win64 Development" -Target="ShaderCompileWorker Win64 Development" -WaitMutex -FromMsBuild

2020-11-05-19-56-12.png

或者加入到UnrealVs的插件里 2020-11-05-19-57-38.png

这样我们就可以进行调试我们的UBT了。

2020-11-05-19-57-59.png

UBT 的功能

  1. 扫描解决方案目录中的模块和插件
  2. 确定需要重新构建的所有模块
  3. 调用UHT来解析c++头文件
  4. 从.Build.cs和.Target.cs创建编译器和链接器选项
  5. 执行特定于平台的编译器(VisualStudio, LLVM)

编译器需要确定编译的先后顺序,因为源码文件之间往往存在依赖关系,假定A文件依赖于B文件,编译器应该保证做到下面两点。

(1)只有在B文件编译完成后,才开始编译A文件。

(2)当B文件发生变化时,A文件会被重新编译.

编译顺序保存在一个叫做makefile的文件中,里面列出哪个文件先编译,哪个文件后编译。在GCC编译器中makefile文件由configure脚本运行生成,这就是为什么编译时configure必须首先运行的原因。在确定依赖关系的同时,编译器也确定了,编译时会用到哪些头文件。

我们编写的Target.cs , Buile.cs 都是为了方便UBT识别并处理模块的依赖而服务的。

UBT的流程

  1. 为每个目标创建一个makefile。如果磁盘上已经存在有效的makefile,则加载该文件【CreateMakefile】。
  2. 将操作导出到JSON文件【ActionGraph.ExportJson】。
  3. 把所有的操作链接起来【ActionGraph.Link】。
  4. 执行操作列表。【ActionGraph.ExecuteActions】。

UBT的入口在UnrealBuildTool.cs里面的 static int Main(string[] ArgumentsArray)

在这里我们将会进入到BuildMode里面。他首先会解析输入参数,构造全局选项,查找所有可用的模式,获取当前使用的模式ToolMode。对于build来说,其最后会调用到执行模式BuildMode.Execute。 之后会调用到

1
public static void Build(...)

建立MakeFile

首先 UBT 调用UEBuildTarget Create会创建一个RulesAssembly,是用来读取和构造项目中的 target.cs 和 Module 的 build.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
	public static RulesAssembly CreateTargetRulesAssembly(FileReference ProjectFile, string TargetName, bool bSkipRulesCompile, bool bUsePrecompiled, FileReference ForeignPlugin)
		{
			RulesAssembly RulesAssembly;
			if (ProjectFile != null)
			{
				RulesAssembly = CreateProjectRulesAssembly(ProjectFile, bUsePrecompiled, bSkipRulesCompile);
			}
			else
			{
				RulesAssembly = CreateEngineRulesAssembly(bUsePrecompiled, bSkipRulesCompile);

				if (RulesAssembly.GetTargetFileName(TargetName) == null && DirectoryReference.Exists(UnrealBuildTool.EnterpriseDirectory))
				{
					// Target isn't part of the engine assembly, try the enterprise assembly
					RulesAssembly = CreateEnterpriseRulesAssembly(bUsePrecompiled, bSkipRulesCompile);
				}
			}
			if (ForeignPlugin != null)
			{
				RulesAssembly = CreatePluginRulesAssembly(ForeignPlugin, bSkipRulesCompile, RulesAssembly, true);
			}
			return RulesAssembly;
		}

UBT

UAT