现在面临的问题: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
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模板:
14.16.27023