UE4在多版本toolchain共存下无法编译的问题

原创
2019/08/26 17:49
阅读数 2.8K

现在面临的问题:vs2015、vs2017、vs2019共存,同时安装有多个版本的toolchain,多个版本的win10 sdk,在生成UE4.21的sln文件后,用visual studio打开编译时,总是自动用电脑上安装的最新toolchain和win10 sdk来编译,导致因大量语法错误而失败。因此,我设想的是解决toolchain匹配的问题,即让UE4使用我指定的toolchain版本来编译。这就要求我这里必须搞清楚UE4的编译框架原理,它是如何确定toolchain和sdk版本号的。

分析的重点在目录:[UE_4.21\Engine\Source\Programs\UnrealBuildTool]我将这个目录简写为UBT

UBT\ToolChain\UEToolChain.cs瞅了一眼,没啥,估计就是定义了一个toolchain的基类

UBT\Platform\Windows下有很多东西,先看一眼VCToolChain.cs。嗯哼,可以确定这个就是定义了一个ToolChain,它实现了上面toolchain基类接口。这个类里有一个PrintVersionInfo,它打印的第一行日志就是很重要的信息,这条日志信息就是显示在visual studio output窗口的第一条信息。ok,那么EnvVars.Compiler、EnvVars.ToolChainVersion、EnvVars.WindowsSdkVersion就是我要追溯的目标。说白了就是要看EnvVars从哪来的。看构造函数可知EnvVars就是Target.WindowsPlatform.Environment,那么我现在需要追溯Target了,它是ReadOnlyTargetRules。先放一放,看看是哪创建了这个VCToolChain,这个才是重点。用vsc追一下,跳到了UEBuildWindows.cs

class WindowsPlatform: UEBuildPlatform{} 这里面定义了CreateToolChain函数,其注释是“Creates a toolchain instance for the given platform.” 好吧,继续搜索,在UEBuildPlatform基类里发现一个abstract的CreateToolChain接口,这个很好理解,跳过。看另一个地方,UBT\Configuration\UEBuildTarget.cs,有戏!不过有点棘手,这儿有两个分支。立个flag先,一条分支没戏的话,回到这儿查另一个分支:【这里是个flag,记得回来查另一个分支】。先看if分支,Rules是个啥:ReadOnlyTargetRules(UBT\Configuration\TargetRules.cs)。看起来Rules.ToolChainName初始值是null,它支持CommandLine("-ToolChain") 来设值。只有这一种方式来设值吗?【这里是个flag,记得回来调查是否还有其他方式设值】。先来调查一下UEBuildTarget.cs@UEBuildPlatform.GetBuildPlatform(Platform).CreateToolChain(CppPlatform, Rules);从GetBuildPlatform的实现中发现Dictionary<UnrealTargetPlatform, UEBuildPlatform> BuildPlatformDictionary;概念太多,有点晕了。先把UnrealTargetPlatform和UEBuildPlatform区分清楚:UnrealTargetPlatform的声明注释是“The platform we're building for”,完美。那么这里我大致可以这么理解:UEBuildPlatform是对代码编译环境建模,那么在一台确定的机器上实例化时,它就实例化为当前机器的编译环境,比如在windows平台上编译代码的话,UEBuildPlatform最终会实例化为一个WindowsPlatform对象,在Mac平台上编译代码的话,UEBuildPlatform最终会实例化为一个MacPlatform对象。而UnrealTargetPlatform就如其注释所说,是指代码编译的结果想要在其上运行的平台,比如我可以UEBuildPlatform是windows,而TargetPlatform是Linux、Mac、Win32、Win64、Android等。先收一下,回到刚才的BuildPlatformDictionary,它的值是由UEBuildPlatform::RegisterBuildPlatform来修改的,追一下这个函数的所有引用,有UEBuildWindows.cs@WindowsPlatformFactory::RegisterBuildPlatforms【立个flag,以后可以查查】这个看起来不是重点,除了传入参数有个windowssdk值得关注之外。回到刚才Rules的来源问题调查,它是UEBuildTarget的属性,看起来有UEBuildTarget()和CreateTarget()两个函数有对Rules设值,不过凭经验,我倾向于CreateTarget()是我需要追踪的那一个【立个flag,回头查查UEBuildTarget()构造函数是否在其他地方调用】。

开始有点绕了,我要聚焦一下:按照我上面选择的理想路径,应该是UE4先要实例化并注册UEBuildPlatform,再由UEBuildPlatform创建指定的UnrealTargetPlatform的ToolChain(这个很合理,最终目标是编译出可以在TargetPlatform上运行的成果,所以不管当前的UEBuildPlatform是啥,都要根据TargetPlatform拿到对应平台的ToolChain。这应该就是上面那个Dict的含义:定义一个平台矩阵,即平台映射关系,每个BuildPlatform都对应若干它支持的TargetPlatform,这些对应关系就是通过上面的Register注册的,之后可以查询这些关系)。ToolChain的参数就是UEBuildTarget里的Rules。这应该就是一个交叉编译的实现。那么,我还是重点来看看TargetRules的来龙去脉。新起点就是这个UEBuildTarget.cs@UEBuildTarget::CreateTarget().

仔细瞅了半天,这个CreateTarget里面的所有函数调用,初看起来都没有修改TargetRules.ToolChainName,那就 【立个flag,稍后详细研究CreateTarget里面其他的函数调用】. 继续追CreateTarget的调用:UBT\UnrealBuildTool.cs@UnrealBuildTool::RunUBT()。先把大流程追完,uproject右键菜单项里"Generate Visual Studio project files",这个是在注册表里的[Computer\HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj],其动作是[Computer\HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj\command]里的["D:\Program Files (x86)\Epic Games\Launcher\Engine\Binaries\Win64\UnrealVersionSelector.exe" /projectfiles "%1"]这个UnrealVersionSelector程序的代码本地没有,不追究了。打开一个uproject文件生成的sln文件,查看工程属性,在NMake->常规->生成命令行中看到["D:\Program Files\Epic Games\UE_4.21\Engine\Build\BatchFiles\Build.bat" RuntimeMeshExamplesEditor Win64 Development "$(SolutionDir)$(ProjectName).uproject" -WaitMutex -FromMsBuild]。打开Build.bat看一下,发现了UnrealBuildTool.exe的调用,它的Main函数在UBT\UnrealBuildTool.cs@UnrealBuildTool::Main, 里面调用了GuardedMain,GuardedMain里又调用了RunUBT,到这里终于全部串起来了。

刚才是从问题点追溯,现在从正向再来一遍。注意到Build.bat里的注释:

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

UnrealBuildTool.exe的调用是UnrealBuildTool.exe %* -DEPLOY,而从刚才NMake命令行["Build.bat" RuntimeMeshExamplesEditor Win64 Development "$(SolutionDir)$(ProjectName).uproject" -WaitMutex -FromMsBuild]可知,%*的值应该是[RuntimeMeshExamplesEditor Win64 Development "$(SolutionDir)$(ProjectName).uproject" -WaitMutex -FromMsBuild]不包含中括号。%1 is the game name说的是RuntimeMeshExamplesEditor,%2 is the platform name说的是Win64, %3 is the configuration name说的是Development. 把所有这些参数,外加-DEPLOY,都传到Main函数里去看看。

在GuardedMain里看到,如果给Build.bat或NMake命令行中增加"-Verbose"参数,可以查看到更详细的编译日志。 如果有"-FromMsBuild"参数,则日志记录属性在这里都可以看到,所有日志是打印到StartupTraceListener里的StringBuilder缓存里的,后面TextWriterTraceListener处创建了日志文件,文件路径是BuildConfiguration.LogFileName决定的。

继续跟踪,UEBuildWindows.cs@WindowsPlatform::ValidateTarget() 中有创建VCEnvironment,该函数里有查询ToolChain版本号的代码。从这段代码可以很清楚的看到,VCEnvironment优先使用WindowsTargetRules里指定的CompilerVersion和WindowsSdkVersion分别作为toolchain和winsdk的版本号。如果这两个值为null,那么会使用WindowsPlatform里DefaultVisualStudioToolChainVersion和DefaultVersion指定的toolchain和winsdk,如果这两个值为null,那么会遍历系统安装的所有toolchain和winsdk,并优先选用最新的版本。

完美!!!记录几个点:toolchain的目录在[VC\Tools\MSVC],查找winsdk列表的方法在UEBuildWindows.cs@WindowsPlatform::UpdateCachedWindowsSdks()函数。一般在C:\Program Files (x86)\Windows Kits\10目录,根据其include里的目录来判断,并不是在include下的都是win10 sdk,有可能只是crt sdk。

如果我要指定特定的toolchain和winsdk版本,怎么办呢?注意在UnrealBuildTool.cs@GuardedMain函数里有一行(L512)XmlConfig.ReadConfigFiles(XmlConfigCache),这个就是给用户机会介入TargetRules的设置的,比如UEBuildWindows.cs@WindowsTargetRules的设置,这里读取了下面两文件:

  • C:\Users\xin.zhou\AppData\Roaming\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml
  • C:\Users\xin.zhou\Documents\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml。

还有L624的 ConfigCache.ReadSettings()。这个函数里有遍历读取如下文件:

  • Engine\Config\Base.ini
  • Engine\Config\BaseEngine.ini
  • Engine\Config\NotForLicensees\BaseEngine.ini
  • D:\bj\Demos\RuntimeMeshComponent-Examples-master\Config\DefaultEngine.ini
  • D:\bj\Demos\RuntimeMeshComponent-Examples-master\Config\NotForLicensees\DefaultEngine.ini
  • D:\bj\Demos\RuntimeMeshComponent-Examples-master\Config\NoRedist\DefaultEngine.ini
  • C:\Users\xin.zhou\AppData\Local\Unreal Engine\Engine\Config\UserEngine.ini
  • C:\Users\xin.zhou\Documents\Unreal Engine\Engine\Config\UserEngine.ini
  • D:\bj\Demos\RuntimeMeshComponent-Examples-master\Config\UserEngine.ini

在ConfigCache.cs@ReadHierarchy()中还可以看到通过命令行参数"-ini:Engine: "的方式增加BuildConfiguration文件,在这里也可以设定BuildConfiguration属性。参数中的Engine是ConfigHierarchyType类型,可以有其他值。

BuildConfigure.xml模板:

<?xml version="1.0" encoding="utf-8"?> 
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration"> 
  <WindowsPlatform> 
    <CompilerVersion>14.16.27023</CompilerVersion> 
  </WindowsPlatform> 
</Configuration>

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
小保哥博主
UEBuildTarget.cs@L2749:GetModuleIntermediateDirectory()函数里将DebugGame的Configuration转成了Development。Garbage
2019/09/20 13:53
回复
举报
小保哥博主
往前一直追到:RulesCompiler.cs@L597:CreatePluginRulesAssembly中的ModuleFiles里的bCanBuildDebugGame属性。
2019/09/20 15:09
回复
举报
小保哥博主
继续追PluginModuleContext
2019/09/20 15:12
回复
举报
小保哥博主
就是这里,ModuleContext的bCanBuildDebugGame属性默认为false导致的。
2019/09/21 14:11
回复
举报
小保哥博主
在具体工程的vs里start without debugging时,会调用automation工具代码中的CreateProjectRulesAssembly。而在启动工程编辑器界面后,plugins菜单的package功能会调用CreatePluginRulesAssembly,这两者的DefaultModuleContext里bCanBuildDebugGame的值是不一样的。
2019/09/21 18:02
回复
举报
小保哥博主
结合上面的两点分析,这里加了一行代码到CreatePluginRulesAssembly函数中: // Find all the modules ModuleRulesContext PluginModuleContext = new ModuleRulesContext(Scope, PluginFileName.Directory); #region added by zbj PluginModuleContext.bCanBuildDebugGame = true; #endregion added by zbj PluginModuleContext.bClassifyAsGameModuleForUHT = !bContainsEngineModules; region/endregion里为添加的代码。
2019/09/21 19:53
回复
举报
小保哥博主
定制plugin的package方案:F:\UE4_Folder\UE_4.23\Engine\Source\Programs\AutomationTool\AutomationUtils\Automation.cs@GlobalCommandLine类中添加一个自定义参数public static CommandLineArg PackageDebug = new CommandLineArg("-PackageDebug");同时在下面Automation类的开头Help区域添加相应描述字句。最后在上一条中所说的地方增加判断,若有PackageDebug参数,就额外编译Debug版本。还需要修改RunUAT.bat中的%UATExecutable% %* %UATCompileArg% -packagedebug
2019/09/18 23:34
回复
举报
小保哥博主
该评论暂时无法显示,详情咨询 QQ 群:912889742
小保哥博主
"F:\UE4_Folder\UE_4.23\Engine\Build\BatchFiles\RunUAT.bat"这个bat文件在执行plugin菜单的package功能时会调用,如果删掉这个文件,执行plugin的package操作时会立即报错。把F:\UE4_Folder\UE_4.23\Engine\Source\Programs\AutomationToolLauncher下的AutomationToolLauncher.csproj添加到F:\UE4_Folder\UE_4.23\Engine\Source\Programs\AutomationTool下的sln中,并利用System.Diagnostics.Debugger.Launch();来挂载调试器开启调试。
2019/09/18 22:13
回复
举报
小保哥博主
BuildTool里面的很多cs文件加Log后,需要自行编译UnrealBuildTool.exe,这样新加的Log才会起作用。
2019/09/12 19:21
回复
举报
小保哥博主
"F:\Program Files (x86)\Epic Games\UE_4.21\Engine\Source\Programs\AutomationTool\AutomationTool_Mono.sln"这里面可以自行编译UnrealBuildTool.exe并调试,上面的后半部分正向研究就是这么干的。
2019/09/12 19:20
回复
举报
更多评论
打赏
12 评论
0 收藏
0
分享
返回顶部
顶部