第一章比较路线并给出团队推荐架构,第二至第八章完成实践,第 6.6 节说明 Horde、单机 Cook 和共享 DDC 的落地分工。
目标:让开发者从一台 Windows 开发机和一台构建 Mac 出发,完成可重复的 iOS 构建,拿到最终 IPA、与它匹配的完整主程序 dSYM Bundle,以及可追溯的日志与版本记录。
实践主线:UE 5.6 C++ Game 项目 → Windows UAT → 远程 Mac 原生编译 → Windows 打包 → Shipping / Ad Hoc IPA + dSYM ZIP → macOS 核验 → 已注册真机验收。
核对日期:2026-11-11。配置和产物处理已对照 Epic 官方 5.6.1-release 源码,提交 6978b63c8951e57d97048d8424a0bebd637dde1d。本版明确团队推荐架构:Horde分配独立任务,一次iOS Cook由一台Windows的本地进程完成,再结合共享DDC与受控Mac构建。命令和脚本是工程实践模板;本次没有你的 UE 项目,没有执行真实的 Windows→Mac 打包或真机验收。
Windows + 远程 Mac,与纯 macOS 构建有什么区别
iOS 原生 C++ 的编译、链接仍在 Mac。 Windows 路线的价值是把开发、资源处理和构建调度留在 Windows,由 Mac 提供 Apple 工具链能力。Windows 不会把 Visual C++ 编译出的 Win64 程序转换成 iOS 程序。[1][3]
两条路线的工作分工
| 工作 | Windows 发起 + 远程 Mac | 纯 macOS 构建 |
|---|---|---|
| UE 编辑、编辑器 / 插件构建 | Windows 编辑器 / Win64 模块 | Mac 编辑器 / Mac 模块 |
| iOS 资源 Cook | 本指南在 Windows 执行 | 在 Mac 执行,也可使用已有 Cook 数据 |
| Metal 着色器 | Windows 安装匹配工具后本地处理 | Mac 的 Apple 工具链处理 |
| iOS C++ 编译、链接 | 远程 Mac | 本地 Mac |
| 主程序 dSYM | Mac 生成,按 manifest 回传 Windows | Mac 生成并保留 |
| 签名材料 | Windows 导入可导出的证书私钥 / Profile;所需材料同步给 Mac | Mac 管理签名身份与 Profile |
| 最终 IPA | Windows 默认 Legacy / iPhonePackager:stub 加入 Cook 内容并按配置重新签名 | 默认 Modern Xcode 可生成归档,再导出 IPA;也可走 Legacy |
| 环境维护 | Windows UE / C++、SSH、Mac Xcode | Mac UE / C++、Xcode |
| Apple 原生调试 | Xcode 在 Mac;两端查看日志 | Xcode 与 UE 集中在 Mac |
Windows UAT 在 5.6.1 源码中默认选择 Legacy;Mac 的 Modern/Legacy 由配置与参数决定。不能把 Mac 上“Archive 后导出 IPA”的操作,直接写成 Windows Package Project 的固定后续步骤。 本指南显式传递 Legacy 参数,固定打包主机和远程编译端的路线。[3][4][5][6]
这条路线中,Mac 在原生构建阶段完成链接,生成用于后续打包的签名 stub;Windows 收到这些编译产物后加入已 Cook 内容,完成最终 IPA 的重新打包和签名。因此,最终交付目录在 Windows,并非“把所有 Cook 产物再送回 Mac 才能生成 IPA”。[3][4][5]
收益与成本
| 判断点 | 实际影响 |
|---|---|
| 团队主要使用 Windows | 保留 UE、VS、资源处理与多平台工作区 |
| 已有高性能 Windows 机器 | Cook / Windows Metal 能利用其资源;iOS C++ 仍受 Mac 性能限制 |
| 多名开发者共享 Mac | 能统一 Apple 工具链、复用机器;要管理并发、签名、存储和排队 |
| Mac 是否必须安装 UE | 官方说明日常远程构建不要求预装 UE / 完整项目,所需构建文件会同步过去 |
| 初次全量构建 | 多了同步与多机协调,不保证比同等性能 Mac 本地构建更快 |
| 后续增量构建 | 可复用缓存;纯 Mac 同样支持缓存与增量构建 |
| 原生 SDK / 签名错误定位 | Windows 与 Mac 两端都有日志,链路更长 |
性能取舍属于工程判断,没有固定加速倍数。比较时用相同项目、配置、资源、SDK 和冷 / 热缓存;不要拿远程缓存命中与 Mac 每次从零编译整个引擎作比较。[1][6]
选型建议: Windows 是团队主要开发平台时,使用 Windows + 主构建 Mac;个人以 iOS SDK 和 Xcode 调试为主、Mac 性能足够时,纯 macOS 通常更容易维护。下面完成 Windows 路线的全部交付步骤。
面向 Windows 团队的推荐架构
在UE 5.6、Windows为主要开发平台、需要完整IPA/dSYM交付的前提下,推荐“一台Windows负责一个iOS构建任务,单机Cook调优,团队共享派生数据,Mac原生构建按资源排队”。 这是基于官方能力和维护成本的工程选择;具体进程数及硬件配置需用项目实测,没有跨项目通用的“最快参数”。
| 层次 | 推荐做法 | 能力边界 |
|---|---|---|
| 独立任务调度 | 已使用Horde的团队用BuildGraph将iOS、Android或其他独立构建分到对应Windows Agent;已有CI也可继续调度本文脚本 | Horde分配图中的任务与产物依赖,不自动拆分同一次iOS Cook |
| 一次iOS Cook | 固定在该次Windows Agent上执行;从CookProcessCount=1建立基线,再比较2、4 | UE5.6 Cook Director与Worker只在同一台机器运行 |
| 缓存 | 保留兼容的本地派生数据;同一机房/办公网的团队加共享DDC,优先复用已有可用服务 | Shared DDC、Zen Cook输出与任务产物库是不同层次 |
| Apple工具链 | iOS任务使用指定Mac,起步每台Mac同一时间只跑一个iOS任务;以后按隔离与实测扩容 | Mac负责C++编译、链接、dSYM/stub;Horde排队与SSH远程编译是两套机制 |
| 发布结果 | Windows完成IPA和receipt列出的dSYM ZIP收集,Mac核验签名/UUID,真机验收 | 不把缓存命中、任务成功或IPA文件存在当完整交付 |
上表中Horde和多进程Cook的边界由官方文档明确支持;Mac的起步并发、缓存优先顺序属于本指南建议。UE5.6没有默认的远程Cook Worker机制,不能把一个iOS包的资源自动分给多台Windows Cook后汇总。 不同平台/项目的独立任务可以并行,一次Cook的多个Worker是单机并行。[16][17][20]
先用第2至第8章完成单任务交付,再按第6.5节调优单机Cook,最后按第6.6节接入团队调度与缓存。多机增加的是独立任务吞吐量;不会自动缩短同一个iOS Cook的关键路径。
明确交付目标,准备环境与参数
第一次实践建议使用 Shipping + Ad Hoc:优化后的发布配置,Apple Distribution 签名,Profile 包含测试设备 UDID,可直接安装到这些已注册设备。先用 Development 跑通连接 / 签名,再切到 Shipping / Ad Hoc。[9][10]
| 模式 | 编译配置 | 证书 / Profile | -distribution | 用途 |
|---|---|---|---|---|
| Development | Development | Apple Development / iOS App Development | 否 | 开发和设备调试 |
| AdHoc(主线) | Shipping | Apple Distribution / Ad Hoc | 是 | 安装到 Profile 中的设备 |
| AppStore | Shipping | Apple Distribution / App Store Connect | 是 | TestFlight / App Store 提交 |
Shipping 是编译配置,-distribution 是分发打包,Profile 决定签名用途。三者要匹配。App Store Profile 的包不能当作 Ad Hoc 包直接安装。[4][9][10]
工具版本
| 机器 | 安装 / 准备 |
|---|---|
| Windows | UE 5.6,安装 iOS 平台支持;VS 2022 推荐 17.14(最低 17.8),C++ 游戏开发组件;Apple 官网版 iTunes / iOS 支持组件 |
| Windows C++ 工具链 | UE文档推荐 MSVC 14.38.33130、Windows SDK 10.0.22621.0 或更新版本,.NET 8.0 |
| Windows Metal | Apple Metal Developer Tools for Windows 4.4 |
| Mac | UE 5.6 文档推荐 Sonoma 14.7 + Xcode 16.1;最低列出 Sonoma 14.5 + Xcode 16 |
| Mac 核验 | Python 3.9+、Xcode Command Line Tools |
| Apple 团队 / 设备 | 有证书与 Profile 管理权限,至少一台登记 UDID 的 iPhone / iPad |
VS / MSVC 工具集需要满足 UE 5.6 官方兼容要求;不能只按“装了 VS”判断。.NET 组件按实际引擎安装处理。[7][8][9][11]
当前商店提交要求: Apple 自 2026-04-28 起要求上传 App Store Connect 的应用使用 Xcode 26 或更新版本和 iOS 26 等对应 SDK。Xcode 16.1 是 UE 5.6 文档基线,不能据此保证 2026 年的上架提交。若目标是当前上架,先完成 UE 5.6 分支与全部插件对所需新工具链的兼容验证,再使用本文 AppStore 模式。[13]
填好全文使用的参数
| 参数 | 示例 | 如何确定 |
|---|---|---|
| EngineRoot | C:\Epic\UE_5.6 | 内部存在 Engine\Build\BatchFiles\RunUAT.bat |
| ProjectFile | D:\UEProjects\MyGame\MyGame.uproject | 完整 .uproject 路径 |
| TargetName | MyGame | Source\MyGame.Target.cs 的 Game Target,不能填 MyGameEditor |
| BundleIdentifier | com.example.MyGame | 与 Apple App ID 完全一致 |
| Team ID | ABCDE12345 | 示例占位值,替换为真实团队 ID |
| Mac / user | 192.168.1.20 / uebuild | 可连接的地址与用户名 |
| Development Profile | MyGame_Development.mobileprovision | 实际安装的文件名 |
| Ad Hoc Profile | MyGame_AdHoc.mobileprovision | 实际安装的文件名 |
| SigningCertificate | Apple Distribution: Example Company (ABCDE12345) | UE 识别到的完整名称 |
| OutputRoot | D:\IOSDeliveries | 交付输出父目录 |
脚本针对 有自己 C++ Game Target 的普通 App。纯 Blueprint 会复用 UnrealGame 目标与符号;不要拿其他引擎版本的 dSYM 配它。要直接使用本文脚本,可先添加空 C++ 类,形成项目自己的 Game / Editor Target,并验证编辑器构建。Framework 输出、多 Target、自定义输出路径需按实际 receipt 调整。[6]
在 Mac 准备 Xcode、签名身份与 SSH
3.1 确认完整 Xcode 与 iOS SDK
打开 Xcode 完成首次启动、组件和许可证处理,安装 iOS 平台支持。在 Terminal 执行:
xcode-select -p
xcodebuild -version
xcodebuild -showsdks
xcrun --sdk iphoneos --show-sdk-path
xcodebuild -checkFirstLaunchStatus
检查点:Developer 路径指向完整 Xcode,版本正确,存在 iphoneos,首次启动检查通过。仅安装 Command Line Tools 不等于完整 iOS 构建环境。[8]
选错 Xcode 时,按真实路径切换并重新检查:
sudo xcode-select --switch /Applications/Xcode_16.1.app/Contents/Developer
示例路径必须替换。构建期间保持同一 Xcode / SDK;切换后重建、重验。
3.2 创建标识、签名与 Profile
在 Apple Developer 的 Certificates, Identifiers & Profiles 中:
- 注册 App ID
com.example.MyGame,启用实际 capabilities。 - 登记测试设备 UDID。
- 准备 Apple Development 证书和 iOS App Development Profile。
- 准备 Apple Distribution 证书和 Ad Hoc Profile,加入测试设备。
- 从 Keychain Access 导出证书及对应私钥为
.p12,设置密码;下载.mobileprovision。
只有 .cer 不能代替含私钥的 .p12。按远程文档要求把签名身份装到 Mac 的 System Keychain,Profile 装到构建用户可访问的位置。[1][9][10]
Xcode 16 常用 Profile 目录:
~/Library/Developer/Xcode/UserData/Provisioning Profiles/
5.6.1 源码按 Xcode 版本识别该路径;不要只检查旧版的 ~/Library/MobileDevice/Provisioning Profiles/。[3]
确认 Mac 识别有效签名身份:
security find-identity -v -p codesigning
3.3 启用 Remote Login
Sonoma 中打开 System Settings > General > Sharing > Remote Login,允许构建账号登录。记录 IP / 用户,保证 Windows→Mac 22 端口互通,账号能写构建目录。[1]
默认远程根目录为 ~/UE5/Builds/<Windows机器名>;可设置 RemoteServerOverrideBuildPath。真实目录以 [Remote] Using base directory ... 日志为准。[1][3]
官方推荐先在 Mac 本地构建一次,排除工具链 / 签名问题。如果临时用 UE 做这一检查,它与“日常远程 Mac 不必预装完整 UE”并不冲突。[1]
在 Windows 导入签名,验证实际 UE 连接
4.1 Windows 编辑器先能正常构建
安装 UE iOS 支持与 VS C++ 组件,确认项目 C++ / 插件的 Win64 编辑器模块可构建,地图可打开。本文示例的 Editor 构建:
$EngineRoot = 'C:\Epic\UE_5.6'
$ProjectFile = 'D:\UEProjects\MyGame\MyGame.uproject'
& "$EngineRoot\Engine\Build\BatchFiles\Build.bat" `
MyGameEditor Win64 Development "-Project=$ProjectFile" -WaitMutex
if ($LASTEXITCODE -ne 0) { throw 'Windows Editor build failed' }
MyGameEditor 要替换为真实 Editor Target。这是编辑 / Cook 前置准备,不生成 iOS IPA。[7][11]
安装官方要求的 iTunes / iOS 支持组件与 Metal Developer Tools 4.4。[1][8]
4.2 Windows 导入 .p12 与 .mobileprovision
先确保构建账号能使用并导出签名私钥。 “P12 含私钥”“已导入 Windows”和“Windows 私钥可导出”是三个条件;缺少最后一个条件,会影响远程构建导出临时签名材料。[3][15]
在执行 UE / UAT 的 同一个 Windows 用户账号 下,双击受控位置的 .p12,打开证书导入向导:
- 存储位置选择“当前用户(Current User)”,输入 P12 密码。
- 在私钥相关选项中勾选 “将此密钥标记为可导出(Mark this key as exportable)”。
- 将证书导入当前用户的“个人(Personal)”证书存储区,完成导入。[15]
如果已经通过 UE 的 Certificate 导入按钮安装了 P12,也要完成下面的检查。不要只看 UE 的绿色有效状态就跳过私钥可导出确认。向导要求选“可导出”,不等于此刻需要额外导出一份私钥文件。
导入后的手工检查:
- 按
Win + R,输入certmgr.msc,打开个人 > 证书。 - 找到与本轮 Profile 匹配的 Apple Development / Apple Distribution 证书,打开证书,确认显示具有对应私钥。
- 右键该证书,选择
所有任务 > 导出。检查 “是,导出私钥(Yes, export the private key)” 是否可选,然后取消向导;此项不可选时,先确认私钥是否存在、是否属于当前构建账号以及导入时是否设为可导出。 - 若原导入不符合要求,使用原始 P12 和密码按上述设置重新导入,重新检查;无原始私钥时回到签名材料管理者处理,单独下载
.cer无法补出私钥。[15]
接着打开 Project Settings > Platforms > iOS,设置 Bundle Identifier,用 Provision 按钮导入 .mobileprovision,手动选择已安装的对应证书和 Profile,并确认有效。SigningCertificate 使用 UE 实际识别的证书名称;P12 文件名是导入材料,不应通用地写成证书名称。[3][9]
5.6.1 远程代码会在 Windows 找 Profile、导出临时 P12 并送到 Mac;最终 Windows iPhonePackager 的重新签名也使用对应身份。只在 Mac 安装证书,不能完成本文的 Windows 手工签名路线。CI 的账号若与桌面用户不同,需要在 CI 执行账号的证书上下文重新确认上述条件。[3][4][9]
随附 Build-IOS.ps1 -DryRun 检查项目参数与配置,不会代替这项证书存储区 / 私钥可导出检查。
Windows Profile 常用目录:
%LOCALAPPDATA%\Apple Computer\MobileDevice\Provisioning Profiles\
MobileProvision / ProvisionFile 要填实际安装的文件名,而不是 Apple 页面上的显示名。[3]
4.3 生成并测试 UE 的 SSH Key
在 Remote Build 设置填 192.168.1.20、用户名 uebuild,关闭 Enable Secondary Remote Mac,点击 Generate SSH Key 并完成安装。第一次连接确认目标机器与主机指纹,密钥位置以 UE 显示值为准。[1]
默认候选路径:
%APPDATA%\Unreal Engine\UnrealBuildTool\SSHKeys\192.168.1.20\uebuild\RemoteToolChainPrivate.key
先测端口:
Test-NetConnection -ComputerName 192.168.1.20 -Port 22
再用 UE 随附 cwrsync SSH 测密钥;替换路径 / 用户:
$EngineRoot = 'C:\Epic\UE_5.6'
$RemoteHost = 'uebuild@192.168.1.20'
$Key = Join-Path $env:APPDATA `
'Unreal Engine\UnrealBuildTool\SSHKeys\192.168.1.20\uebuild\RemoteToolChainPrivate.key'
$KeyForCygwin = '/cygdrive/' + $Key.Substring(0, 1).ToLower() + `
($Key.Substring(2) -replace '\\', '/')
& "$EngineRoot\Engine\Extras\ThirdPartyNotUE\cwrsync\bin\ssh.exe" `
-i $KeyForCygwin -o BatchMode=yes $RemoteHost 'xcodebuild -version'
if ($LASTEXITCODE -ne 0) { throw 'UE SSH / Xcode preflight failed' }
预期无需交互就能输出目标 Mac 的 Xcode 版本。系统 OpenSSH 通过不代表 UE 的 cwrsync / 密钥权限也通过。[3]
UNPROTECTED PRIVATE KEY FILE / 0660 按官方文档修复密钥权限,再测试。私钥 / P12 保存在受控凭据位置,不放到网站或公开仓库。[1]
配置项目:签名、Cook、dSYM 与构建路线
5.1 开启完整 dSYM Bundle
Project Settings > Platforms > iOS > Build 启用两个选项:
- Generate dSYMs for code debugging and profiling。
- Generate dSYMs as a bundle for third party crash tools。[2]
第一个启用生成,第二个输出标准目录形式的 .dSYM Bundle,并生成可回传的 .dSYM.zip。[2][3]
本文单主 Mac、手工签名的 Config/DefaultEngine.ini:
[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
BundleIdentifier=com.example.MyGame
RemoteServerName=192.168.1.20
RSyncUsername=uebuild
bUseRSync=True
bSupportSecondaryMac=False
bAutomaticSigning=False
MobileProvision=MyGame_AdHoc.mobileprovision
SigningCertificate="Apple Distribution: Example Company (ABCDE12345)"
bGeneratedSYMFile=True
bGeneratedSYMBundle=True
bGenerateXCArchive=False
bShipForBitcode=False
首次 Development 换成 Development Profile / 证书,Ad Hoc 再换回 Distribution 身份。bGenerateXCArchive=False 与 dSYM 生成无关;Windows 的该选项不能生成 XCArchive。[3][4]
不要在 Config/IOS/IOSEngine.ini 等位置覆盖成不同值。脚本以 DefaultEngine.ini 为实践配置来源;已有平台覆盖需先明确有效配置再调整检查。
5.2 配置实际发布内容
在 Project Settings > Packaging:选对应 Build Configuration;Ad Hoc / App Store 勾 For Distribution,Development 取消;Pak / IoStore 与 UAT 参数一致;实际发布地图进入 Maps to Cook,Game Default Map 正确;动态 / 软引用资源通过 Asset Manager、Primary Asset Label 或额外 Cook 目录覆盖。[11]
Config/DefaultGame.ini 示例:
[/Script/UnrealEd.ProjectPackagingSettings]
BuildConfiguration=PPBC_Shipping
ForDistribution=True
bUsePakFile=True
bUseIoStore=True
+MapsToCook=(FilePath="/Game/Maps/Main")
/Game/Maps/Main 是占位地图。编辑器可运行不代表 Cook 资源完整;实际登录、支付 / SDK 资源、关卡和动态加载路径都要验收。
5.3 固定 stub → Windows IPA 流程
本文显式使用 -legacyxcode,并用 -ubtargs=-legacyxcode 传给 UBT / 远程编译端,固定已核对的路线,不要求改引擎 BaseEngine.ini。不要同时传 -modernxcode。[3][4]
从 Windows 发起完整构建
6.1 构建实际做什么
- Windows UAT / UBT 读项目、目标、Profile 和签名配置。
- 所需构建文件和签名材料同步到主 Mac。
- Mac 编译 / 链接 iOS C++,生成 dSYM 和签名 stub;产物按 manifest 回传。
- Windows Cook 内容,Stage 组织发布资源。
- Windows iPhonePackager 把内容加入 stub,按配置签名最终 IPA。
- UAT Archive 收集 IPA,随后独立收集本次 receipt 的 dSYM ZIP。[3][4][11]
6.2 Shipping / Ad Hoc 完整 UAT 命令
使用 PowerShell,替换变量,确保上一章已选 Ad Hoc 签名:
$EngineRoot = 'C:\Epic\UE_5.6'
$ProjectFile = 'D:\UEProjects\MyGame\MyGame.uproject'
$TargetName = 'MyGame'
$ArchiveDir = 'D:\IOSDeliveries\MyGame-AdHoc-FirstBuild\UATArchive'
$UatArgs = @(
'BuildCookRun', '-installed', '-nop4',
"-project=$ProjectFile", "-target=$TargetName",
'-platform=IOS', '-clientconfig=Shipping',
'-build', '-cook', '-stage', '-pak', '-iostore',
'-package', '-distribution', '-archive', "-archivedirectory=$ArchiveDir",
'-nodebuginfo', '-unattended', '-utf8output',
'-legacyxcode', '-ubtargs=-legacyxcode',
'-provision=MyGame_AdHoc.mobileprovision',
'-certificate=Apple Distribution: Example Company (ABCDE12345)'
)
& "$EngineRoot\Engine\Build\BatchFiles\RunUAT.bat" @UatArgs
if ($LASTEXITCODE -ne 0) { throw 'UAT failed: inspect the earliest failed stage' }
| 参数 | 本次作用 |
|---|---|
| -installed | Launcher / Installed Build;源码引擎移除此参数 |
| -target | 发布的 Game Target,不是 Editor |
| -build -cook -stage -package | 原生构建、转换资源、组织内容、打包 |
| -pak -iostore | 本例资源容器形式 |
| -distribution | 分发打包,匹配 Distribution 证书 / Profile |
| -archive -archivedirectory | 收集发布包,不是 Xcode Archive,不自动收集 dSYM |
| -nodebuginfo | 不把调试文件复制进 Stage;不关闭 dSYM 生成和远程产物回传 |
| -legacyxcode / -ubtargs | 固定打包主机和编译端的实践路线 |
此处 -nodebuginfo 把符号与 App 内容分开。第五章开启生成,第七章从原始 Binaries / receipt 收集;不要把这个参数理解成“完全不生成 dSYM”。[3][4]
6.3 用随附脚本一起完成构建和收集
把 Build-IOS.ps1 与 Verify-IOS.py 放在同一目录,如 D:\BuildTools\UE56IOS\。脚本检查版本 / 关键配置,创建唯一输出目录,保存日志,检查 UAT 返回值,按本次 receipt 收集 dSYM ZIP,并生成哈希清单。
先预览:
& 'D:\BuildTools\UE56IOS\Build-IOS.ps1' `
-EngineRoot 'C:\Epic\UE_5.6' `
-ProjectFile 'D:\UEProjects\MyGame\MyGame.uproject' `
-TargetName 'MyGame' `
-BundleIdentifier 'com.example.MyGame' `
-ProvisionFile 'MyGame_AdHoc.mobileprovision' `
-SigningCertificate 'Apple Distribution: Example Company (ABCDE12345)' `
-OutputRoot 'D:\IOSDeliveries' `
-Mode AdHoc -DryRun
确认后去掉 -DryRun 执行。源码引擎加 -SourceEngine。如果团队 PowerShell 策略限制脚本,按团队已有授权方式执行,不要全局放宽策略。
Development 用 -Mode Development、Development 证书 / Profile,并同步改项目设置;AppStore 同理。只改 -Mode 不够,远程编译读取的项目签名配置也必须匹配。
脚本的 -CookProcessCount 默认1,明确固定基线。完成第6.5节的比较后,可在同一调用增加 -CookProcessCount 4,或使用实际有效的其他值。该参数经UAT的AdditionalCookerOptions传给本机Cooker,不会创建另一台Windows上的Worker。[16]
6.4 判断本次成功
检查日志中的 Mac / 用户、base directory、Xcode / SDK、编译和链接、dSYM 生成、Downloading build products、Cook、iPhonePackager、Package 与 Archive,最终成功退出。[3][4]
独立输出目录、当前日志、receipt 和第八章 UUID 核验一起排除旧产物。“目录里已有一个 IPA”不能证明本次成功。
6.5 首次交付通过后,再选择 Cook 优化
先按默认主线完成一轮 IPA / dSYM 收集、签名和 UUID 核验以及真机验收,再根据项目耗时选择优化。下面选项在 UE 5.6.1 UAT 中可用,属于不同层面的配置,不是生成 IPA / dSYM 的必需参数。[4]
| 可选参数 / 做法 | 实际作用 | 使用条件和检查点 |
|---|---|---|
| -zenstore | 把 Cook 输出保存到 Zen Storage Server | 确认目标 UE 所使用的 Zen 服务正常、存储空间足够,Stage / Package 能读取对应数据;它本身不会把单机 Cook 变成多机并行 |
| -iterativecooking | 尝试复用已有 Cook 数据,只处理需要重新 Cook 的内容 | 保留匹配的 Cook 缓存并检查本次日志;更换引擎、插件、平台或资源规则后重新确认缓存有效性 |
| -compressed | 对资源容器启用压缩打包 | 比较包体、打包耗时与真机加载表现;不代表 iOS C++ 编译变快 |
| -nocompileeditor | 跳过本轮 UAT 的编辑器编译 | Windows Editor Target 和插件已按本版本构建好,否则 Cook 可能使用不匹配的模块 |
| -AdditionalCookerOptions="-cookprocesscount=N" | 在当前Windows机器使用多进程Cook,Director分配资源并汇总本机Worker结果 | 先测1、2、4,检查内存、GC、日志及插件兼容性;UE5.6不支持远程Cook Worker |
| Horde + BuildGraph | 调度图中的独立构建任务,处理任务依赖和中间产物传输 | 可分到不同Agent,但不自动把同一次iOS Cook拆到多台Windows |
ZenStore 的角色是 Cook 输出存储;增量 Cook 的角色是资源结果复用。使用 -pak -iostore 打包后,应确认运行需要的资源已包含在交付包中,并在目标设备不连接开发用 Zen 服务的条件下验证启动与关键资源加载。[4][11]
要在第 6.2 节的 直接 UAT 命令 上尝试,放在 $UatArgs 定义之后、RunUAT.bat 调用之前。按需取消对应行的注释,逐项验证,不必全部启用:
# 可选:Zen 服务与 Cook 输出存储已准备好
# $UatArgs += '-zenstore'
# 可选:已有兼容 Cook 缓存
# $UatArgs += '-iterativecooking'
# 可选:测试资源容器压缩的包体与加载表现
# $UatArgs += '-compressed'
本版 Build-IOS.ps1 保留基础构建参数,没有 -ZenStore / -IterativeCooking 开关;上面的示例修改的是直接 UAT 参数数组,不能把这些开关直接传给辅助脚本。
优先验证单机多进程Cook,再决定是否使用Zen Cook输出和增量Cook。 官方将多进程Cook列为Beta功能:更多Worker会使用更多内存,CPU/内存饱和或GC频繁会使Cook更慢;聚合多个包数据的插件也可能需要IMPCollector适配。小项目保持1个进程可能更快,World Partition等场景应特别检查Worker负载和日志。[16]
选择一种设置方式,避免互相覆盖:
; Config/DefaultEditor.ini
[CookSettings]
CookProcessCount=4
使用直接UAT命令时,在第6.2节调用前追加:
# 整个参数是一个PowerShell数组元素
$UatArgs += '-AdditionalCookerOptions=-cookprocesscount=4'
若已有AdditionalCookerOptions,应把 -cookprocesscount=4 合并到原来那个参数中。使用本文脚本时直接传 -CookProcessCount 4;脚本每次明确传入该值,默认1会覆盖DefaultEditor.ini中的默认进程数。4只是起始比较值,不是“4台Windows”。[16]
希望复用原生编译缓存时,不要每次构建前都删除 Intermediate\Build\IOS;该目录与 Cook 缓存的职责不同。只清掉原生中间结果、同时启用 -iterativecooking,只能说明资源侧可能增量,不代表 C++ 编译也增量。定位缓存问题时可以按实际失败阶段清理相关缓存,但清理后的第一次构建耗时应单独记录。[3][4]
优化后仍保留相同的交付条件:最终 IPA、该次主程序 dSYM ZIP、receipt、日志、签名 / UUID 核验以及设备测试。测量时分别记录原生编译、Cook、打包和同步耗时,不能用一个“总耗时变短”推断所有阶段都变快。
6.6 团队落地:任务调度、缓存与Mac资源
第一步:先确定优化单次耗时,还是增加团队吞吐量
一个项目的单次iOS发布慢,先分析Mac编译、Windows Cook、同步、打包各阶段;只有Cook是主要瓶颈时,优先调Cook进程与资源缓存。多个独立构建排队时,才增加Windows Agent或拆分不同平台任务。没有必要仅为一个iOS任务先搭建整个Horde服务。
本指南推荐起步拓扑:
现有CI,或Horde Server + BuildGraph
├─ Windows Agent A:本次iOS任务
│ ├─ Cook Director + 本机Cook Worker
│ ├─ SSH使用指定Mac A:C++ / Link / dSYM / stub
│ └─ 本机Stage / IPA / 符号收集
├─ Windows Agent B:Android或另一项独立构建
└─ Windows Agent C:Win64、编辑器或独立测试任务
同一机房的Agent → 已配置的Shared DDC
每次交付产物 → CI/Horde产物库或团队归档
最终iOS交付 → Mac签名/UUID核验 → 真机验收
这是任务分工建议,不是Horde自动生成的流水线。A的Cook Worker全部在A机器上;B、C不参与A的这一轮iOS资源拆分。[16][17]
第二步:明确三类数据,优先配置Shared DDC
| 数据 | 用来解决什么 | 推荐存放 / 验证方式 |
|---|---|---|
| Local / Shared DDC | 复用可再生成的shader、纹理等派生数据,减少重复处理 | 本地快速缓存加同区域Shared DDC;从LogDerivedDataCache确认实际使用 |
| Cook输出 / ZenStore | 保存针对平台Cook后的内容,供Stage / Package读取 | 与本次平台、资源规则和引擎版本匹配;包应通过不连接开发服务的设备验收 |
| 构建 / 交付产物 | 保存任务中间文件和最终IPA、dSYM、receipt、版本和日志 | CI/Horde产物库或受控归档,按同一Job/修订追溯 |
Shared DDC不等于Cook资源分片,也不等于最终包仓库。 -zenstore 控制Cook输出存储;配置共享Zen DDC无需因此给每次UAT加 -zenstore。UE5.4及以后默认本地DDC使用Zen;新建同区域团队服务可以评估Windows共享Zen,已有稳定文件共享DDC先复用,跨地域再考虑Cloud DDC。[18][19]
下面示例以管理员已按官方文档部署并验证可访问的共享Zen服务为前提,替换主机与namespace;只在项目 Config/DefaultEngine.ini 加客户端配置,不修改iOS签名部分:
[StorageServers]
Shared=(Host="http://shared-ddc.example.internal:8558", Namespace="mygame.ddc", EnvHostOverride=UE-ZenSharedDataCacheHost, CommandLineHostOverride=ZenSharedDataCacheHost, DeactivateAt=60)
标准UE配置中的ZenShared通过ServerID=Shared使用这一项。启动编辑器/构建,检查 LogDerivedDataCache 出现ZenShared、期望主机和namespace以及可用状态;在编辑器的Derived Data / View Cache Statistics检查延迟。自定义DDC图需确认保留ZenShared节点;不可只写一行配置就认定生效。[18][19]
Zen共享DDC在UE5.6的官方适用范围是可信LAN/VPN:服务没有认证,配置与访问限于团队既有可信网络。它不是要公开到公网的交付服务。官网给出了专门的服务端安装、数据目录、启动和客户端核验步骤;本文没有实际部署该服务。[19]
Horde发布任务的工作区可以回到干净状态,但DDC缓存可以在工作区之外或共享服务中复用。这与日常 -iterativecooking 依赖Cook输出复用不同;“干净发布工作区 + Shared DDC”仍有缓存收益。[17][18]
第三步:已使用Horde时,按独立任务配置BuildGraph
UE5.6官方构建自动化教程以Perforce Stream、Horde Server及Agent为前提;已有其他CI的团队可先继续调用本文脚本,采用相同的工作区、缓存和Mac资源规则,无需为Cook性能强行迁移。[17]
Horde落地时按以下顺序配置与验证:
- 用同一修订的引擎、项目、插件和构建脚本定义BuildGraph节点;明确每个节点的输入、输出和依赖。
- 在Horde配置project、stream、agent type及job template;agent type映射到实际机器池和工作区。
- 起步为iOS任务使用专用Windows Agent池,配对指定Mac;其他池执行Android/Win64等独立任务。
Build → Cook → Stage → Package → 收集IPA/dSYM起步保持在同一个Windows Agent组内,避免搬运未明确声明的Binaries/receipt/Cook状态。它内部仍会通过SSH使用Mac。- 同一份完整delivery交给Mac核验节点或现有Mac验证任务,成功后才归档/分发。跨Agent步骤显式声明产物,不能只依赖源机器上的临时目录。[17][20]
同一个BuildGraph Agent组内节点顺序执行、保留中间目录;独立Agent组按依赖可并行。将Build与Cook进一步拆到不同机器之前,应先明确全部原生编译产物、receipt、插件资源和Cook数据的传输契约,而不是只复制一个IPA或一个Content目录。[20]
第四步:让Mac排队规则覆盖SSH远程构建
Horde租用了Windows Agent,不等于它自动租用了该任务经SSH连接的Mac。推荐起步每台Mac对应一个专用Windows iOS Agent,同一时刻只运行一个iOS任务;其他Windows仍可并行其他平台任务。其他CI任务、开发者直接运行UAT也应遵守同一Mac排队规则。
需要更多iOS并发时,优先增加经验证的Windows/Mac配对,或实现覆盖所有入口的Mac资源锁,再评估账号、Keychain、工作目录、网络和内存隔离。不能让多个Windows仅填写同一个RemoteServerName就认为并发安全。此为本指南基于共享签名和远程工作区的工程建议,不是Horde默认替你执行的限制。[3][17]
第五步:用实测选参数
先记录单进程基线,再比较1、2、4个Cook进程,保证同一代码/资源修订、平台、配置及缓存条件。分别记录总耗时、Cook耗时、Mac编译、同步、打包、峰值内存、GC和Worker错误。稳定减少关键阶段耗时且通过交付验收的值才进入日常模板。[16]
| 场景 | 起步方案 | 何时扩展 |
|---|---|---|
| 首次打通或小项目 | 单Windows+单Mac,CookProcessCount=1 | 实际Cook瓶颈明显再测多进程 |
| 大型资源项目 | 先测2/4个本机Cook进程,复用Local / Shared DDC | 验证插件与内存后增加有效进程数 |
| 多平台/多个独立Job排队 | Horde/现有CI分配不同Windows,iOS固定Mac配对 | 队列持续饱和再扩机器池 |
| 当前正式IPA/dSYM交付 | 相同修订、明确缓存策略,签名/UUID和真机检查完整 | 验收后再逐项加Zen Cook输出或增量Cook |
Horde提高任务吞吐量,单机多进程Cook缩短可能的Cook阶段,DDC减少重复派生数据工作。三者结合形成推荐方案;它们不是跨机器Cook Worker,不能承诺把同一个iOS全量构建按机器数量线性加速。
收集 IPA 与完整 dSYM,形成交付目录
7.1 原始产物位置
普通 C++ Game、Target MyGame 的典型结果:
D:\UEProjects\MyGame\Binaries\IOS\
Distro_MyGame-IOS-Shipping.ipa
MyGame-IOS-Shipping.dSYM.zip
MyGame-IOS-Shipping.target
名字受 Target、配置、架构和项目类型影响,以本次 receipt / 日志为准,不在所有项目硬编码。[3][4]
脚本从本次 UATArchive 找最终 IPA,从对应 *.target 的 BuildProducts 找符号。不要手选其他配置 / Target 的同名文件。
7.2 为什么 -archive 不够
5.6.1 Windows Legacy GetFilesToArchive 收集 IPA;dSYM ZIP 属于 Mac 编译产物,由 dsymutil 生成,按 manifest 回传,需要独立收集。[3][4]
符号 ZIP 应保留完整 Bundle:
MyGame-IOS-Shipping.dSYM/
Contents/
Info.plist
Resources/
DWARF/
MyGame-IOS-Shipping
保留原始 .dSYM.zip,在 Mac 用 ditto 解包,避免 Windows 复制改变结构或符号链接。崩溃平台需要 Bundle / ZIP 时,上传与该 IPA 配对的一份。[3][12]
7.3 交付结构
MyGame-Shipping-AdHoc-<时间>-<唯一ID>/
build.log
UATArchive/
...本次 UAT 的 IPA...
delivery/
Distro_MyGame-IOS-Shipping.ipa
symbols/
MyGame-IOS-Shipping.dSYM.zip
...receipt 列出的其他 dSYM ZIP...
build.target
Build.version
build.json
build.log
artifact-manifest.json
Verify-IOS.py
verification.json ← 第八章成功后生成
收集完成只表示文件存在、有哈希。build.json 标记尚未通过签名 / UUID 核验;成功核验后才确认主程序符号与最终包匹配。
7.4 第三方动态 Framework 的符号
主程序 dSYM 不覆盖每个独立动态 Framework。需要供应商提供 与该 Framework 二进制 UUID 匹配 的 dSYM。参与主程序静态链接的代码与独立 Framework 要区分。[12]
需要 SDK 符号时,先把该版本供应商的 .dSYM.zip 放进独立目录,构建脚本增加 -AdditionalSymbolDirectory 'D:\SDKSymbols\CurrentVersion'。脚本将其与 UE 符号一起收集并生成哈希清单,然后运行 --require-framework-symbols。如果供应商只有 .dSYM 目录,可先在 Mac 用 ditto -c -k --keepParent Foo.framework.dSYM Foo.framework.dSYM.zip 保留 Bundle 打包。脚本不会凭空生成供应商已剥离的符号。
核验签名与 UUID,再做真机验收
8.1 传输整个交付目录
用团队已有文件共享 / SFTP 工具,把 delivery/ 整体复制到 Mac,如 ~/IOSDeliveries/MyGame-AdHoc/,保持 IPA、原始 dSYM ZIP、receipt 和 manifest 一起移动。
8.2 运行核验脚本
替换 Bundle ID、Team ID、设备 UDID:
python3 "$HOME/IOSDeliveries/MyGame-AdHoc/Verify-IOS.py" \
"$HOME/IOSDeliveries/MyGame-AdHoc" \
--bundle-id com.example.MyGame \
--team-id ABCDE12345 \
--mode ad-hoc \
--device-udid YOUR_REGISTERED_DEVICE_UDID
脚本核对文件哈希、ZIP 完整性、receipt、Bundle ID、arm64、主程序与 dSYM 全部 UUID / 架构、代码签名、Profile 有效期、Team、签名模式和设备覆盖。它不会重新签名、修改 IPA 或安装应用。
成功生成 verification.json,列出 IPA 哈希与 UUID,并明确 真机验收尚待执行。需要完整动态 Framework 符号覆盖时,在同一命令最后加 --require-framework-symbols。默认报告会列出 Framework 符号缺口;主程序匹配不等于所有 SDK 符号齐全。
8.3 手工检查 UUID 的命令
替换 IPA、符号 ZIP、.app 的真实文件名:
Delivery="$HOME/IOSDeliveries/MyGame-AdHoc"
CheckDir=$(mktemp -d /tmp/mygame-verify.XXXXXX)
ditto -x -k "$Delivery/Distro_MyGame-IOS-Shipping.ipa" "$CheckDir/ipa"
ditto -x -k "$Delivery/symbols/MyGame-IOS-Shipping.dSYM.zip" "$CheckDir/symbols"
App="$CheckDir/ipa/Payload/MyGame.app"
Executable=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$App/Info.plist")
xcrun dwarfdump --uuid "$App/$Executable"
xcrun dwarfdump --uuid "$CheckDir/symbols/MyGame-IOS-Shipping.dSYM"
codesign --verify --deep --strict --verbose=2 "$App"
security cms -D -i "$App/embedded.mobileprovision" > "$CheckDir/profile.plist"
/usr/libexec/PlistBuddy -c 'Print :Name' "$CheckDir/profile.plist"
/usr/libexec/PlistBuddy -c 'Print :ExpirationDate' "$CheckDir/profile.plist"
先看 Payload/ 再设置 App;两个 dwarfdump 的 UUID / 架构逐项一致。文件名相同、时间接近、都是 Shipping 都不能替代配对。[12]
8.4 真机安装与功能验收
Ad Hoc / Development 通过 Xcode Window > Devices and Simulators 或团队设备工具安装。检查 UDID 覆盖,完成设备所需开发者模式 / 信任,再启动。[9][14]
至少验收:启动地图、登录 / 支付 / SDK 初始化、网络与权限、关键关卡、动态资源加载、真实设备画面与性能。安装、启动、符号核验分别记录。需要符号化验收时,用可控测试崩溃或已知报告验证崩溃平台的 UUID 与堆栈解析。[12]
8.5 App Store / TestFlight 分支
先满足第二章工具链要求与项目兼容性,再换 App Store 证书 / Profile,脚本 -Mode AppStore,Mac 核验 --mode app-store,无需设备 UDID。该 IPA 用于渠道提交,不做 Ad Hoc 安装。
用团队提交工具或 Mac Transporter 提交 IPA,保留本版本 dSYM、日志、receipt 和报告。Windows Legacy 已生成 IPA,不必为“得到 IPA”重新完整 Mac 构建;团队另需 .xcarchive 时,再规划 Mac Modern / Archive 交付。[6][10][13]
排障与交付完成核对
| 最早失败阶段 / 症状 | 首先检查 | 通过条件 |
|---|---|---|
| SSH / RSync | Remote Login、用户、UE 密钥、cwrsync 权限 | UE SSH 可无交互执行 Xcode 命令 |
| Profile / ExportCertificate | 当前构建账号的 Windows Profile、匹配证书私钥是否可导出、模式配置 | 私钥存在且可导出,签名材料同步成功 |
| SDK invalid / 无 iphoneos | xcode-select、完整 Xcode、SDK 与 UE 兼容 | 实际 Xcode / SDK 正确 |
| C++ 编译 / 链接 | 最早错误、iOS插件、架构、库 | Mac 编译 / 链接完成 |
| Metal / Cook | Metal 4.4、shader日志、资源规则 | 本次 Cook 完成 |
| IPA有、dSYM无 | 两个生成配置、receipt、Mac动作、回传 | 找到主程序 dSYM ZIP |
| Archive只有IPA | Windows Legacy 正常边界 | 独立收集符号完成 |
| UUID不匹配 | 旧符号、配置 / Target / SDK混用 | UUID / 架构完全对应 |
| 签名核验失败 | 最终重签、Profile / entitlements、嵌入签名 | 最终IPA核验通过 |
| 安装失败 | AppStore误当AdHoc、UDID、过期、信任 | 目标真机安装成功 |
交付完成核对
勾选记录开发者实际执行结果,网站不会自动检测 UE 环境。
官方依据与模板验证范围
| 编号 | 官方来源 | 支撑内容 |
|---|---|---|
| S1 | UE 5.6 Remote Mac Builds | 主Mac、SSH与准备 |
| S2 | UE 5.6 iOS Settings | dSYM和远程配置 |
| S3 | Epic 5.6.1官方UBT源码 | AppleExports、ToolChain/RemoteMac、IOSToolChain、UEBuildIOS、TargetReceipt |
| S4 | iOS UAT源码 / ProjectParams | IPP、命名、Archive边界、nodebuginfo、UAT参数 |
| S5 | iPhonePackager CookTime | stub重打包与重签 |
| S6 | UE Modernized Xcode Workflow | Mac归档 / 导出与内容目标 |
| S7 | UE 5.6 Visual Studio Setup | Windows C++环境 |
| S8 | UE Development Requirements / Windows Metal | 工具链基线、Metal4.4 |
| S9 | UE Provisioning and Signing | 签名材料与项目标识 |
| S10 | Apple Ad Hoc Provisioning | 证书、设备与Profile |
| S11 | UE 5.6 Build Operations | Build / Cook / Stage / Package / Archive |
| S12 | Apple Crash Symbolication | 主程序与Framework符号、UUID匹配 |
| S13 | Apple Upcoming Requirements | 2026-04-28 SDK提交要求 |
| S14 | UE Packaging iOS Projects | IPA安装;OS标签主要差在安装说明 |
| S15 | Microsoft 证书导入向导 / 证书及私钥导出 | Windows私钥可导出的导入选项和手工确认 |
| S16 | UE5.6 Multi-Process Cooking | 单机Director/Worker限制、CookProcessCount与内存/插件边界 |
| S17 | UE5.6 Horde Build Automation / Horde教程 | 任务图、Agent池、工作区和中间产物 |
| S18 | UE5.6 Derived Data Cache | Local / Shared / Cloud DDC与派生数据复用 |
| S19 | UE5.6 Shared Zen DDC | 服务端部署、StorageServers客户端配置和实际核验 |
| S20 | UE5.6 BuildGraph Elements | Node依赖、Agent组和输入/输出声明 |
Epic GitHub需要关联Epic的账号权限。网站不包含引擎源码或签名材料;随附脚本是独立工程模板。
官方文档和指定源码已核对。网站、下载及脚本参数 / 核验逻辑可单独验证;真实UE编译、Windows证书导入、Mac远程构建、IPA运行和新SDK兼容性,需要开发者在自己的环境按检查点完成,不能把网站发布当成这些验收通过。