[UE]理解UnrealBuildTool

2020/03 28 22:03

UBT在哪

在Windows10下,用UE4+VS2019新建一个空工程ue01,看此工程的属性,可以看到在Nmake切页上,“生成命令行”为:

"D:\Program Files\Epic Games\UE_4.24\Engine\Build\BatchFiles\Build.bat"
ue01 Win64 DebugGame 
-Project="$(SolutionDir)$(ProjectName).uproject" 
-WaitMutex 
-FromMsBuild

查看Build.bat文件,内容如下:

@echo off
setlocal enabledelayedexpansion

REM The %~dp0 specifier resolves to the path to the directory where this .bat is located in.
REM We use this so that regardless of where the .bat file was executed from, we can change to
REM directory relative to where we know the .bat is stored.
pushd "%~dp0\..\..\Source"

REM %1 is the game name
REM %2 is the platform name
REM %3 is the configuration name

IF EXIST ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe (
        ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe %*
		popd

		REM Ignore exit codes of 2 ("ECompilationResult.UpToDate") from UBT; it's not a failure.
		if "!ERRORLEVEL!"=="2" (
			EXIT /B 0
		)
		 
		EXIT /B !ERRORLEVEL!
) ELSE (
	ECHO UnrealBuildTool.exe not found in ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe 
	popd
	EXIT /B 999
)

即在Windows平台下,Build.bat调用的是
D:\Program Files\Epic Games\UE_4.24\Engine\Binaries\DotNET\UnrealBuildTool.exe

编译Unreal Build Tool

在D:\Program Files\Epic Games\UE_4.24\Engine\Source\Programs\UnrealBuildTool目录,
用VS2019打开UnrealBuildTool.csproj,编译一下就得到了,输出在
D:\Program Files\Epic Games\UE_4.24\Engine\Binaries\DotNET\UnrealBuildTool.exe

有什么用?

为了支持代码的多种编译形式和多平台支持,甚至将相同的代码编译成一个.lib或者一个.dll(用于支持热加载),虚幻开发团队决定开发自己的构建工具,这意味着不再使用传统的makefile或者MSbuild,而是使用Unreal Build Tool。因此在虚幻中进行C++开发以及整合第三方库,与传统方式比起来会有一些不同。

Unreal Build Tool由C#编写,且作为整个虚幻编译过程中第一个编译步骤。当你运行“GenerateProjectFiles”(一个批处理文件,用于在Window平台下生成Visual Studio的解决方案和工程),这第一个步骤就是在Source/Programs/UnrealBuildTool/UnrealBuiltTool.csproj工程下执行MSBuild来编译这个“Unreal Build Tool”。

所以,Unreal Build Tool 其实就是一个命令行程序,却可以完成很多事情,比如生成工程文件、执行Unreal Header Tool、为各种不同的平台个构建风格调用编译器(Compiler)和连接器(Linker)。这个过程后面还会详细解释。

Modules(模块)

虚幻引擎中的模块其实就是一系列源文件。可以是C++模块或者C#模块。C#模块使用.csproj(Visual Studio C#工程描述文件)作为它的工程文件。

对于C++模块,就有些奇怪了。模块使用一个“模块名.build.cs”文件来定义,这个文件跟vcxproj(Visual Studio C++工程描述文件)类似,一个Private目录用于存放私有的头文件和源文件;以及一个Public目录用于存放公开的头文件和源文件。

Public目录下的头文件默认会暴露给其他模块(在添加模块依赖后可以直接include这个目录下的文件),Private目录下的则不会(除非手动添加PrivateIncludePath)。

所以,创建一个C++模块的规则就是,在Engine/Source 或者 你的游戏工程/Source 下面创建一个新的目录并添加一个.Build.cs文件。这个模块对其他模块的依赖也需要在.Build.cs文件夹中配置好。

对于整合第三方代码也是一样,创建一个目录,一个 .Build.cs,并在 .Build.cs 文件中描述该模块如何构建以及哪些头文件需要暴露给其他模块。

一个C++模块可以被编译为一个整体(monolith),也可以编译为其他模块(一个可执行文件,Dll等)的一部分,又或是一个.dll用于热加载。这就是虚幻提供的灵活性。

参考Modules

Targets(目标)

虚幻中的一个Target可以被理解为前面提到的一个“编译形式”。

UnrealBuildTool支持构建多个目标类型:

  • Game游戏 – 一个独立的游戏
  • Client客户端 – 与游戏相同,但不包含任何服务器代码。适用于网络游戏。
  • Server服务器 – 与游戏相同,但不包含任何客户端代码。适用于网络游戏中的专用服务器。
  • Editor编辑器 – 扩展虚幻编辑器的目标。
  • Program程序 – 构建在虚幻引擎之上的独立实用程序。

目标是通过扩展名为.Target.cs的C#源文件声明的,并存储在项目的Source目录下。 可以通过它来设置一堆Target所依赖的定义和其他属性等。即使像UnrealHeaderTool这样的Unreal实用程序也是作为Program目标构建的。

参考TargetFiles

生成项目文件

首先,要做的是运行GenerateProjectFiles。但是……会发生什么?

  • Unreal Build Tool被构建了。
  • 批处理文件调用了类似如下的命令(取决于您的项目,Visual Studio版本,平台等)
    -ProjectFiles -nodummyconfigs -game -engine -2017 “-project=Path\To\Your\Project.uproject” -Platforms=Win64+XboxOne+UWP64 -noSolutionSuffix
  • Unreal Build Tool会在引擎和游戏目录搜索所有带有.Build.cs拓展的文件来发现所有定义的模块。
  • Unreal Build Tool会搜索所有带有.Target.cs拓展的文件来发现所有定义的目标。
  • 它将生成一个包含所有目标作为构建配置和所有模块作为项目的解决方案。

C#项目只是源文件夹中的.csproj文件。C ++项目并不完全是“标准”项目。它不再调用MSBuild,而是调用UnrealBuildTool!

构建C++项目

在Unreal中构建C++项目时,您可以看到(基于vcxproj的NMakeBuildCommandLine属性)将调用与此类似的命令行:

C:\Path\To\Your\Engine\Build.bat TargetName Win64 Debug "$(SolutionDir)$(ProjectName).uproject" -waitmutex $(AdditionalBuildArguments) -2017

它的背后其实又调用了UnrealBuildTool!

那么,UnrealBuildTool在这儿的作用是:

  • 编译目标。它在运行时编译了.Target.cs代码(使用C#编译器)来获取构建属性。这是UnrealBuildTool从中获取大部分定义和平台信息的地方。某些属性(例如bBuildEditor)表示你需要的是构建编辑器。它会创建一个WITH_EDITOR定义,然后由编译器转发到源文件。以实现源代码中的条件编译:#if WITH_EDITOR 条件编译。
  • 解析所有依赖模块,包含来自.Target.cs和.Build.cs(模块)的依赖。
  • 将编译所有依赖模块的Build.cs,以获取有关如何构建每个模块的额外属性。
  • 解析哪些模块使用了共享编译头(即.Build.cs文件中包含SharedPCHHeaderFile属性,比如CoreUObject,Core,Engine等)。
  • 解析哪些模块依赖于UObject模块。
  • 对所有依赖于UObject的模块运行Unreal Header Tool,这时虚幻引擎会注入一些行为到你的类中,强制你在文件中加入由Unreal Header Tool生成的“.generated.h”头文件。
  • 基于Unreal Header Tool生成的代码,解析所有Include路径。
  • 基于解析后的路径、定义、外部库等,生成一系列会在目标环境执行的命令列表:
  • 为共享预编译头调用编译器(CL.EXE)
  • 调用编译器来编译源文件(CL.EXE)
  • 调用链接器(LINK.EXE)
  • 调用所有这些操作

显然,这些步骤比较简略。还有更多的细节和设置,比如你是否使用CLR,是否使用Mono,是否使用Clang等等。但大致的步骤和上面所讲的一样。