WINDOWS → iOS · 开发者实践指南

在 Windows 发起,
交付 IPA 与 dSYM。

先看与纯 macOS 构建的区别,再一步步完成环境、签名、远程编译、产物收集和真机验收。

版本依据 UE 5.6.1Shipping / Ad Hoc 主线附完整实践脚本
完整交付链路BUILD → COLLECT → VERIFY
01 / WINDOWS发起构建与处理资源

UAT · Project / Signing · Cook

SSH / RSync · 文件与签名材料
02 / REMOTE MAC原生编译与符号生成

C++ · Link · dSYM · Signed stub

回传产物 · Windows 打包与收集
03 / WINDOWS DELIVERYIPA + 完整 dSYM ZIP

IPP · Receipt · Logs · Hash manifest

最后在 Mac 核验签名 / UUID,再执行真机验收
01
理解两条构建路线

编译、Cook、签名与打包分别在哪执行。

02
Windows 发起完整构建

复制命令,或使用构建与收集脚本。

03
交付匹配的 IPA 与符号

验证 UUID、签名、Profile 和真实设备。

i

第一章比较路线并给出团队推荐架构,第二至第八章完成实践,第 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 打包或真机验收。

01

Windows + 远程 Mac,与纯 macOS 构建有什么区别

iOS 原生 C++ 的编译、链接仍在 Mac。 Windows 路线的价值是把开发、资源处理和构建调度留在 Windows,由 Mac 提供 Apple 工具链能力。Windows 不会把 Visual C++ 编译出的 Win64 程序转换成 iOS 程序。[1][3]

构建分工与团队调度选择路线,查看能力边界
Windows 调度和打包,Mac 提供原生工具链
Windows · UAT / CookMac · C++ / dSYM / stubWindows · IPA / 符号收集

本指南固定 Legacy 流程:签名材料在 Windows 可用,Mac 构建产物回传,Windows iPhonePackager 输出最终 IPA。

两条路线的工作分工

工作 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的关键路径。

02

明确交付目标,准备环境与参数

第一次实践建议使用 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]

03

在 Mac 准备 Xcode、签名身份与 SSH

3.1 确认完整 Xcode 与 iOS SDK

打开 Xcode 完成首次启动、组件和许可证处理,安装 iOS 平台支持。在 Terminal 执行:

macOS · 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 时,按真实路径切换并重新检查:

macOS · Terminal
sudo xcode-select --switch /Applications/Xcode_16.1.app/Contents/Developer

示例路径必须替换。构建期间保持同一 Xcode / SDK;切换后重建、重验。

3.2 创建标识、签名与 Profile

在 Apple Developer 的 Certificates, Identifiers & Profiles 中:

  1. 注册 App ID com.example.MyGame,启用实际 capabilities。
  2. 登记测试设备 UDID。
  3. 准备 Apple Development 证书和 iOS App Development Profile。
  4. 准备 Apple Distribution 证书和 Ad Hoc Profile,加入测试设备。
  5. 从 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 识别有效签名身份:

macOS · Terminal
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]

04

在 Windows 导入签名,验证实际 UE 连接

4.1 Windows 编辑器先能正常构建

安装 UE iOS 支持与 VS C++ 组件,确认项目 C++ / 插件的 Win64 编辑器模块可构建,地图可打开。本文示例的 Editor 构建:

Windows · PowerShell
$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,打开证书导入向导:

  1. 存储位置选择“当前用户(Current User)”,输入 P12 密码。
  2. 在私钥相关选项中勾选 “将此密钥标记为可导出(Mark this key as exportable)”。
  3. 将证书导入当前用户的“个人(Personal)”证书存储区,完成导入。[15]

如果已经通过 UE 的 Certificate 导入按钮安装了 P12,也要完成下面的检查。不要只看 UE 的绿色有效状态就跳过私钥可导出确认。向导要求选“可导出”,不等于此刻需要额外导出一份私钥文件。

导入后的手工检查:

  1. 按 Win + R,输入 certmgr.msc,打开 个人 > 证书。
  2. 找到与本轮 Profile 匹配的 Apple Development / Apple Distribution 证书,打开证书,确认显示具有对应私钥。
  3. 右键该证书,选择 所有任务 > 导出。检查 “是,导出私钥(Yes, export the private key)” 是否可选,然后取消向导;此项不可选时,先确认私钥是否存在、是否属于当前构建账号以及导入时是否设为可导出。
  4. 若原导入不符合要求,使用原始 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

先测端口:

Windows · PowerShell
Test-NetConnection -ComputerName 192.168.1.20 -Port 22

再用 UE 随附 cwrsync SSH 测密钥;替换路径 / 用户:

Windows · PowerShell
$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]

05

配置项目:签名、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:

项目配置 · 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 示例:

项目配置 · 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]

06

从 Windows 发起完整构建

6.1 构建实际做什么

  1. Windows UAT / UBT 读项目、目标、Profile 和签名配置。
  2. 所需构建文件和签名材料同步到主 Mac。
  3. Mac 编译 / 链接 iOS C++,生成 dSYM 和签名 stub;产物按 manifest 回传。
  4. Windows Cook 内容,Stage 组织发布资源。
  5. Windows iPhonePackager 把内容加入 stub,按配置签名最终 IPA。
  6. UAT Archive 收集 IPA,随后独立收集本次 receipt 的 dSYM ZIP。[3][4][11]

6.2 Shipping / Ad Hoc 完整 UAT 命令

使用 PowerShell,替换变量,确保上一章已选 Ad Hoc 签名:

Windows · PowerShell
$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 用随附脚本一起完成构建和收集

选择这次交付的签名用途示例参数需要替换
本轮配置Shipping / Ad Hoc

项目设置选择 Ad Hoc Profile 和 Apple Distribution 身份;需要安装的设备必须在 Profile 中。下面命令调用随附脚本,构建并收集产物。

Windows · PowerShell · 示例调用

把 Build-IOS.ps1 与 Verify-IOS.py 放在同一目录,如 D:\BuildTools\UE56IOS\。脚本检查版本 / 关键配置,创建唯一输出目录,保存日志,检查 UAT 返回值,按本次 receipt 收集 dSYM ZIP,并生成哈希清单。

先预览:

Windows · PowerShell
& '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 调用之前。按需取消对应行的注释,逐项验证,不必全部启用:

Windows · PowerShell
# 可选: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]

选择一种设置方式,避免互相覆盖:

项目配置 · INI
; Config/DefaultEditor.ini
[CookSettings]
CookProcessCount=4

使用直接UAT命令时,在第6.2节调用前追加:

Windows · PowerShell
# 整个参数是一个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签名部分:

项目配置 · INI
[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落地时按以下顺序配置与验证:

  1. 用同一修订的引擎、项目、插件和构建脚本定义BuildGraph节点;明确每个节点的输入、输出和依赖。
  2. 在Horde配置project、stream、agent type及job template;agent type映射到实际机器池和工作区。
  3. 起步为iOS任务使用专用Windows Agent池,配对指定Mac;其他池执行Android/Win64等独立任务。
  4. Build → Cook → Stage → Package → 收集IPA/dSYM 起步保持在同一个Windows Agent组内,避免搬运未明确声明的Binaries/receipt/Cook状态。它内部仍会通过SSH使用Mac。
  5. 同一份完整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全量构建按机器数量线性加速。

07

收集 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 打包。脚本不会凭空生成供应商已剥离的符号。

08

核验签名与 UUID,再做真机验收

8.1 传输整个交付目录

用团队已有文件共享 / SFTP 工具,把 delivery/ 整体复制到 Mac,如 ~/IOSDeliveries/MyGame-AdHoc/,保持 IPA、原始 dSYM ZIP、receipt 和 manifest 一起移动。

8.2 运行核验脚本

替换 Bundle ID、Team ID、设备 UDID:

macOS · Terminal
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 的真实文件名:

macOS · Terminal
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]

09

排障与交付完成核对

最早失败阶段 / 症状 首先检查 通过条件
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、过期、信任 目标真机安装成功

交付完成核对

0 / 8 项已手工确认

勾选记录开发者实际执行结果,网站不会自动检测 UE 环境。

10

官方依据与模板验证范围

编号 官方来源 支撑内容
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兼容性,需要开发者在自己的环境按检查点完成,不能把网站发布当成这些验收通过。