背景问题
最近同事遇到一个编译的问题,如果书写了错误的Log语句,会导致真个unreal build无限卡死:
UE_LOG(LogTemp, Debug, TEXT("Something"));
这里这个Debug
等级其实是不存在的,是一个书写错误,正常情况会报一个编译错误,但在我们的case中,会出现无限等待的情况。
网上也有类似的bug情况: https://answers.unrealengine.com/questions/1003748/error-in-macro-syntax-causes-build-to-hang.html?sort=oldest
可以看到的是cl-filter.exe失去了响应,但也没有更多的线索. 于是想去看看这个过程中发生了什么?
深入UBT的过程
之前一直试用UBT编译,没有好好学习过编译一个cpp发生了什么,UE4是怎么一步步去调用到系统的cl.exe完成一个cpp的编译的
如何调试UBT
- 设为启动项目
- 复制启动参数
获得NMake中Build.bat 后面的参数,这些参数都会送到UnrealBuildTools.exe 中执行
- 填入启动参数
注意要把$(SolutionDir)换成你自己的绝对路径
这样就可以调试整个UBT运行过程了
UBT处理过程
Module
- 输入源码.cpp/.h
- 配置build.cs 定义一些宏以及依赖关系
UBT
- 解析各种宏定义,保存到Module.XXX.Definitions.h
- 处理各种编译参数保存到XXX.obj.response文件中(这个就是编译的主要内容)
- 根据Unity Build策略,合并一个Module.XXX.cpp
- 执行一个ExcuteAction,在window上里面的内容就是一个带参数的 cl-filter命令行
cl-filter.exe
- 本质是cl.exe的一个warp,有个warp的主要目的是通过/showIncluds命令来获取编译过程中正真的包含文件,(这个解析过程可能还存在多语言之类的问题,所以比较复杂),但最终都是把 cl.exe的
include信息
和其他编译日志
分离 - 我的理解:生成的includes文件,可以供UBT做进一步的优化使用(未深入)
追踪各个步骤的中间产物
Defination文件和response文件
在windows上,一般在VCToolChain.cs中产生一些全局定义和编译参数 主要是一些包含路径和宏定义 Plugin的话可以在
代码语言:javascript复制IntermediateBuildWin64UE4EditorDevelopmentXXXModule.XXX.cpp.obj.response
找到这个编译参数文件
这个文件直接决定了后面cl-filter以及cl的编译全部内容
ExcuteAction
这个构造了对应的cl-filter的命令参数 主要有 cl的路径位置,输出的log位置,以及上面提到的response文件 查看这个参数的方法可以直接断点看,
或者加-Verbose参数激活
一个命令行参数大概长这样
代码语言:javascript复制"G:UnrealEngineEngineBuildWindowscl-filtercl-filter.exe"-dependencies=G:G6MMO-DemoPluginsG6PluginG6SkillFrameworkIntermediateBuildWin64UE4EditorDebugSkillBridgeAnimationActionHandle.cpp.txt -compiler="C:Program Files (x86)Microsoft Visual Studio2019ProfessionalVCToolsMSVC14.28.29333binHostX64x64cl.exe" -- "C:Program Files (x86)Microsoft Visual Studio2019ProfessionalVCToolsMSVC14.28.29333binHostX64x64cl.exe" @"G:G6MMO-DemoPluginsG6PluginG6SkillFrameworkIntermediateBuildWin64UE4EditorDebugSkillBridgeAnimationActionHandle.cpp.obj.response" /showIncludes
cl-filter
cl-filter 是cl.exe的封装,我们也可以在UnrealEngineEngineExtrasWindowscl-filter找到他的源码和工程,可以attach或者加上上面的命令行参数进行调试
最终执行的cl.exe,就是使用cl-filter后面的那些参数(主要是response文件) 但是我们需要在Unreal程序的EngineSource的下面,无论是安装版本的UE4还是源码版本的UE4都可以,但是编译结果也和你的工作路径有关
Unity Build
通常UBT会把一堆小的cpp整合成一个大的cpp以减小编译时间(减少io次数) 这个行为就叫做Unity Build UE自己也有一些编译参数控制UnityBuild的效果,比如bUseUnityBuild/bForceUnityBuild/bUseAdaptiveUnityBuild
https://docs.unrealengine.com/4.26/en-US/ProductionPipelines/BuildTools/UnrealBuildTool/BuildConfiguration/
Exclude from unity build
某些时候,单个文件编译单独编译比整合成一个大文件更快
1>[Adaptive unity build] Excluded from XXX unity file: XXX.cpp
那么UE是怎么识别这需要exclude呢,我一开始以为是通过修改时间,实际上看了
Unity.cs中的实现之后了解,实际上是通过git status来识别修改的文件。
相比于git修改过的文件就会标记成exclude from build。
如果你的git仓库有submodule,会导致submodule中的文件无法在根目录被git status识别到,这个需要注意
使用单个文件编译可以检查一些头文件包含缺失的方法,除了在git状态下修改,还有一种方法是配置UnrealVS插件使用快捷键进行单文件编译。
比如#include "CoreMinimal.h"
这个如果忘记写,会导致单文件编译的时候DLLEXPORT无法被识别,这也是我坑了很久的问题
前面的bug解决过程
目前还没有完美解决和理解,但是通过拆解整个UBT的过程,也是弥补了自己的一些知识盲区
参考
UBT调试方法:https://blog.csdn.net/u013412391/article/details/106150390 cl-filter解释:https://papalqi.cn/ubt/ UE4单独编译一个cpp的方法:https://docs.unrealengine.com/4.26/zh-CN/ProductionPipelines/DevelopmentSetup/VisualStudioSetup/UnrealVS/