模块以及目标类型
不再赘述
构建 UBT,创建 VS 工程
GenerateProjectFiles.bat
调用 Engine\Build\BatchFiles\GenerateProjectFiles.bat
该脚本前面的部分是准备编译环境,比如找到 MSBuild,然后调用 MSBuild 构建 UnrealBuildTool
UnrealBuildTool.csproj
是提前写好的,所以可以直接编译
然后就是调用 UnrealBuildTool 来构建 VS 工程。于是开始看 UnrealBuildTool 代码。
如何调试 UnrealBuildTool 生成项目
Engine\Build\BatchFiles\GenerateProjectFiles.bat
里面写得很清楚了,就是用这个命令行参数
1 | -ProjectFiles |
UnrealBuildTool 入口函数根据参数选择创建 ToolMode
调用 Execute
虚函数。不同的 mode 对应不同的构建行为
显然 GenerateProjectFilesMode
类对应构建 VS 工程的逻辑。
我想知道自己创建出来的模块是怎么让 UE 识别到的,于是还是要看看他是怎么生成项目文件的
生成项目时,自定义模块如何被识别
查看 GenerateProjectFilesMode
类的 Execute
实现
可以看到他对于不同平台有不同的项目生成器 Generator
,最终调用 Generator.GenerateProjectFiles
点进来看 Generator.GenerateProjectFiles
,前面的配置就不说了
然后是他寻找 game, target 和 module 的部分
1 | // Build the list of games to generate projects for |
之后就是把找到的这些目标添加到工程
1 | ProjectFile EngineProject; |
如何调试 UnrealBuildTool 构建项目
已经配置好 VS 项目后,直接在 VS 工程中点击调试,在输出窗口中可以看到构建命令
比如我这里的构建命令是
1 | <path to engine>\Engine\Build\BatchFiles\Build.bat -Target="<project name>Editor Win64 Development -Project=\"E:\Unreal Projects\<project name>\<project name>.uproject\"" -Target="ShaderCompileWorker Win64 Development -Quiet" -WaitMutex -FromMsBuild |
去看一下 <path to engine>\Engine\Build\BatchFiles\Build.bat
就可以看到,他只是 UnrealBuildTool.exe
的简单包装
所以完全可以复制粘贴后面的命令行参数,以调试 UnrealBuildTool 项目
UnrealBuildTool 构建模式的逻辑
显然,BuildMode
类对应构建逻辑。
其 Execute
内主要功能是收集构建目标,进行多目标构建。每个目标可以是远端构建或本地构建。
然后它调用 Build
开始对具体目标开始构建。
具体到本地构建 public static void Build(List<TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration, ISourceFileWorkingSet WorkingSet, BuildOptions Options, FileReference WriteOutdatedActionsFile, bool bSkipPreBuildTargets = false)
,每个不能被跳过构建的目标,都生成 makefile
再下一层 static void Build(TargetMakefile[] Makefiles, List<TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration, ISourceFileWorkingSet WorkingSet, BuildOptions Options, FileReference WriteOutdatedActionsFile)
对确认要构建的,具有 makefile 的目标进行处理。
这里面,除了一些构建特性的支持,比如热重载,暂时跳过不看,剩下的主题是引入了一个 Action
的概念。每一项构建一个 Action
,Action
之间有依赖关系,是一个图。ActionGraph.ExecuteActions
对这个图结构发起构建。
这个图结构发起构建的逻辑是,根据构建配置创建不同类型的 Executor
,然后调用 Executor.ExecuteActions
对 Action
列表发起构建
在我的调试中,它创建的是 ParallelExecutor
类
该 Executor
类里面对输入的 Action
列表遍历,转为 BuildAction
列表
单纯从他创建的过程来看,BuildAction
相较于 Action
主要是多了个序号
然后根据 BuildAction
的 .Inner.PrerequisiteItems
创建依赖关系
最终从 BuildAction
列表遍历,创建 BuildAction
队列
对 BuildAction
队列处理构建。这时可以根据依赖关系对 BuildAction
排序
排序之后就可以对每一个 BuildAction
创建构建线程
1 | BuildAction Action = QueuedActions[QueuedActions.Count - 1]; |
之后就是怎么收集这些构建线程的事情了。
这些线程工作完了,UBT 就结束了。于是步进调试到 ParallelExecutor.ExecuteAction
。
其中的核心是
1 | ManagedProcess Process = new ManagedProcess(ProcessGroup, Action.Inner.CommandPath.FullName, Action.Inner.CommandArguments, Action.Inner.WorkingDirectory.FullName, null, null, ProcessPriorityClass.BelowNormal) |
可见,他也是最终调用一个可执行文件,然后传入各种文本作为构建参数
我调试时,Action.Inner.CommandPath.FullName
是
1 | <path to engine>\Engine\Build\Windows\cl-filter\cl-filter.exe |
这个 cl-filter 甚至不在 Engine 的 VS 项目和 Project 的 VS 项目目录里,酷
知道他是 cl 的包装,用来实现一些目的就够了
Action.Inner.CommandArguments
是
1 | -dependencies="E:\Unreal Projects\<project name>\Intermediate\Build\Win64\UE4Editor\Development\<project name>\MyClass.cpp.txt" -compiler="E:\software\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe" -- "E:\software\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe" @"E:\Unreal Projects\<project name>\Intermediate\Build\Win64\UE4Editor\Development\<project name>\MyClass.cpp.obj.response" /showIncludes |
打开看这个 response 文件,可以看到他应该是为了传入编译选项、include 路径、输入输出目录
前面的部分的节选
1 | /bigobj |
后面的部分的节选
1 | /I "C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt" |
到此就差不多了。剩下的,比如 cl 干了什么、cl-filter 干了什么、response 文件是怎么生成的、读 response 之后怎么构建的,应该就太细节了,我没有去调试
虽然我没有调试,但是看了一些博客。cl-filter 这个包装,主要是为了解决,增量构建时,如何找到某次代码修改涉及到的所有的依赖项的问题。