基于Zen Server的Unreal Engine构建产物发布机制分析
摘要
本文试图理解nreal Engine自动化构建系统(BuildGraph)中与Zen Server集成的构建产物发布机制。通过对BuildGraph
脚本的XML定义及其底层C#实现(ZenExportSnapshotTask
)的分析,了解该机制的核心工作流程、技术优势,并明确了其在数据持久化方面的具体行为。
1. 引言
本文聚焦于BuildGraph
系统中名为ZenExportSnapshot
的节点,对其工作原理进行详细解析。
2. Zen Server发布机制的核心组件
该发布机制主要由两部分协同工作:BuildGraph
中的XML声明式节点,以及由该节点调用的后端C#任务实现。
2.1 BuildGraph XML声明
在BuildGraph
脚本(如 BuildAndTestProject.xml
)中,通过<ZenExportSnapshot>
节点来调用发布功能。其关键参数配置如下:
1 2 3 4 5 6 7 8
| <ZenExportSnapshot Project="$(TargetProject)" Platform="$(CookPlatform)" DestinationStorageType="Zen" DestinationZenHost="$(UE-ZenPublishHost)" DestinationIdentifier="$(SnapshotIdentifier)" SnapshotDescriptorFile="$(SnapshotLocalDir)/$(StagedPlatformFolder)/$(SnapshotFilenamePrefix)-zen.json" />
|
-Set:PublishZenSnapshot=true
: 命令行参数,作为激活该功能的主要开关。
UE-ZenPublishHost
: 环境变量,用于指定目标Zen Server的地址。
<ZenExportSnapshot>
节点属性:
Project
, Platform
: 定义了发布内容的基本信息。
DestinationStorageType="Zen"
: 明确指定存储后端为Zen Server。
DestinationZenHost
: 引用UE-ZenPublishHost
环境变量,作为数据上传的目标。
DestinationIdentifier
: 为本次发布提供一个唯一的标识符,通常结合分支名和变更列表(Changelist)生成。
SnapshotDescriptorFile
: 指定生成的快照描述符JSON文件的输出路径。
2.2 后端实现:ZenExportSnapshotTask
该XML节点在执行时会实例化并运行ZenExportSnapshotTask
类。此类的核心职责是作为调度器,收集参数并调用外部命令行工具zen.exe
来完成实际工作。
3. 工作流程深度解析 (结合源码)
ZenExportSnapshotTask
的执行流程确保了构建数据的完整上传。
3.1 数据源定位
任务首先在本地的烘焙输出目录 (Saved/Cooked/<Platform>
) 中查找 ue.projectstore
文件。该JSON文件是在Cook
阶段由引擎生成的,记录了本次烘焙操作的数据在本地Zen Server实例中的存储信息,包括项目ID (ProjectId
) 和操作日志ID (OplogId
)。
源码佐证 (ZenExportSnapshotTask.cs
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| FileReference projectStoreFile = FileReference.Combine(platformCookedDirectory, "ue.projectstore"); ProjectStoreData? parsedProjectStore = null;
if (TryLoadJson(projectStoreFile, out parsedProjectStore) && (parsedProjectStore != null) && (parsedProjectStore.ZenServer != null)) { ExportSourceData newExportSource = new ExportSourceData(); newExportSource._isLocalHost = parsedProjectStore.ZenServer.IsLocalHost; newExportSource._hostName = parsedProjectStore.ZenServer.HostName; newExportSource._hostPort = parsedProjectStore.ZenServer.HostPort; newExportSource._projectId = parsedProjectStore.ZenServer.ProjectId; newExportSource._oplogId = parsedProjectStore.ZenServer.OplogId; newExportSource._targetPlatform = platform; exportSources.Add(newExportSource); }
|
此段代码清晰地展示了任务如何通过解析ue.projectstore
文件来获取源数据的确切位置和标识。
3.2 构建命令行
任务基于ue.projectstore
提供的信息和XML节点传入的参数,构建一个针对zen.exe
的命令行调用。其核心命令为 oplog-export
。
源码佐证 (ZenExportSnapshotTask.cs
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| StringBuilder oplogExportCommandline = new StringBuilder(); oplogExportCommandline.Append("oplog-export --embedloosefiles");
case SnapshotStorageType.Zen: oplogExportCommandline.AppendFormat(" --zen {0}", _parameters.DestinationZenHost);
string hostUrlArg = string.Format("--hosturl http://{0}:{1}", exportSource._isLocalHost ? "localhost" : exportSource._hostName, exportSource._hostPort);
exportSingleSourceCommandline.AppendFormat(" {0} --target-project {1} --target-oplog {2} {3} {4}", hostUrlArg, projectName, destinationOplog, exportSource._projectId, exportSource._oplogId);
|
这段代码展示了如何动态构建一个完整的 oplog-export
命令,它精确地定义了数据同步的源与目标。
3.3 执行数据同步
通过TryExportOplogCommand
函数调用zen.exe
执行上一步构建的命令。此过程涉及实际的网络传输。
源码佐证 (ZenExportSnapshotTask.cs
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private static bool TryExportOplogCommand(string app, string commandLine) { int attemptLimit = 2; int attempt = 0; while (attempt < attemptLimit) { if (TryRunAndLogWithoutSpew(app, commandLine, false)) { return true; } Logger.LogWarning("Attempt {AttemptNum} of exporting the oplog failed, {Action}...", attempt + 1, attempt < (attemptLimit - 1) ? "retrying" : "abandoning"); attempt = attempt + 1; } return false; }
if (_parameters.SkipExport || TryExportOplogCommand(zenExe.FullName, exportSingleSourceCommandline.ToString())) { successfullyExportedSources.Add(exportSource); }
|
zen.exe oplog-export
进程负责执行实际的数据传输。其内部利用Zen Server的CAS特性:
- 数据块化 (Chunking):
oplog-export
首先获取源Oplog引用的所有数据块(Chunks)的哈希列表。
- 差异比对 (Diffing): 它将这个哈希列表与目标Zen Server进行比对,识别出目标服务器上缺失的数据块。
- 增量上传 (Incremental Upload): 仅将目标服务器上不存在的数据块从源服务器拉取并上传。构建的实际二进制数据在此阶段被可靠地传输。
3.4 生成快照描述符 (Snapshot Descriptor)
仅在oplog-export
命令成功执行后,任务才会生成JSON格式的快照描述符文件。
源码佐证 (ZenExportSnapshotTask.cs
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| if ((_parameters.SnapshotDescriptorFile != null) && successfullyExportedSources.Any()) { using (JsonWriter writer = new JsonWriter(_parameters.SnapshotDescriptorFile)) { writer.WriteObjectStart(); writer.WriteArrayStart("snapshots");
foreach (ExportSourceData exportSource in successfullyExportedSources) { WriteExportSource(writer, destinationStorageType, exportSource, exportNames[exportIndex]); exportIndex = exportIndex + 1; }
writer.WriteArrayEnd(); writer.WriteObjectEnd(); } }
|
此段代码的执行条件是successfullyExportedSources.Any()
,即必须至少有一个数据源被成功导出,这再次确认了快照文件的生成是在数据同步之后的步骤。
4. 结论
BuildGraph
中的ZenExportSnapshot
功能并非一个简单的元数据快照工具,而是一个完整的、基于CAS模型的数据发布与同步机制。它的执行流程保证了构建产物的二进制数据被增量、高效地上传至目标Zen Server。生成的快照描述符文件可以被视为数据成功入库的凭证或“提货单”。
该机制的核心优势在于:
- 存储效率: 通过CAS模型,极大地减少了存储相似构建版本时的空间冗余。
- 网络效率: 增量上传的特性显著降低了CI/CD流程中的网络带宽消耗。
- 可靠性: 流程设计确保了只有在数据完全同步后才会生成可供下游使用的快照文件。
因此,在Unreal Engine的自动化流程中,Zen Server不仅可作为开发时的派生数据缓存(Derived Data Cache, DDC),更可作为存储和分发构建产物的可靠后端,是实现高效CI/CD流程的关键基础设施。