Windows 端显示“已连接”,但 .NET MAUI 10 项目仍在 iOS 编译阶段失败,通常不能先归咎于网络。
最快的处理方式是:先在远程 Mac 本机用最小项目构建,再确认 .NET for iOS 工作负载与 Xcode 26.6 是否匹配。Mac 本机也失败,就修工具链;只有 Windows 远程调用失败,才继续排查 Pair to Mac、账户、SSH 或缓存;环境持续漂移且无法隔离时,才考虑重建节点。
这篇文章适合三类人:
- 使用 Visual Studio 从 Windows 连接 Mac 构建 .NET MAUI iOS 项目的开发者;
- 维护远程 Mac 构建节点的 DevOps 工程师;
- 负责工具链升级、签名和发布稳定性的移动研发平台负责人。
最后更新于 2026 年 9 月 5 日,版本信息核实自 Microsoft Learn 的 .NET MAUI 10 文档、dotnet/macios 发布记录 与 .NET MAUI 官方发布记录。后续在 .NET MAUI 10 新服务版本发布、.NET for iOS 更新 Xcode 支持要求、Pair to Mac 文档改版或节点升级工具链后,应重新复核。
先建立四层故障边界
“Pair to Mac 已连接”只说明 Windows 能够通过 SSH 与 Mac 建立构建主机连接,并不代表 Mac 上的 Apple 工具链、远程 SDK、项目目标框架或签名身份已经可用。官方说明中,Visual Studio 会连接 Mac 构建主机,并调用远程主机上的工具完成 iOS 编译与签名;因此连接状态不能代替构建验收。Pair to Mac 的工作方式与 SSH 说明
建议把结果分成四层记录:
| 验收层 | 代表什么 | 失败时优先交给谁 |
|---|---|---|
| Pair to Mac 连接成功 | 主机发现、账户和 SSH 基础连接可用 | Windows 开发者或节点运维 |
| Mac 本机构建成功 | 本机 SDK、工作负载和 Xcode 基本匹配 | 应用开发者 |
| Windows 远程构建成功 | 远程 SDK、缓存、环境变量和 IDE 调用路径一致 | CI 工程师 |
| 签名与发布成功 | 证书、私钥、描述文件、设备或归档链路完整 | 发布工程师 |
不要只复制最后一行错误。Windows 端和 Mac 端都应保存完整二进制日志,并标记第一个有效错误,例如 Xcode 版本验证失败、找不到 SDK、远程服务初始化失败,或 CodesignKey、CodesignProvision 无法匹配。后续每次修复都使用同一个项目、配置、目标框架和运行目标复测,否则很容易把多个故障混在一起。
开发者的最小构建证据
应用开发者首先要确认项目实际使用的版本,而不是只看项目文件里引用的 .NET MAUI NuGet 包。.NET MAUI 本身与 Android、iOS 平台工作负载并不是完全相同的版本实体;iOS 兼容性应继续查看对应的 .NET for iOS 发布记录与当前 SDK 状态。
在远程 Mac 上,先使用占位路径创建或准备一个全新的最小项目:
cd /path/to/diagnostic
dotnet --info
dotnet workload list
dotnet workload search maui
xcode-select -p
xcodebuild -version
项目名、路径和目标框架均使用实际诊断值替换,例如:
dotnet build /path/to/MinimalMauiApp.csproj \
-f net10.0-ios \
-c Debug \
-v:diag \
-bl:/path/to/logs/mac-local.binlog
这里要确认的是:
dotnet --info显示的 SDK 是否为团队锁定版本;dotnet workload list是否存在对应的maui、ios或相关 .NET for iOS 工作负载;xcode-select -p是否指向完整的 Xcode.app,而不是只安装了命令行工具;xcodebuild -version返回的是否确实是 Xcode 26.6,而不是旧路径残留;net10.0-ios、ios-arm64、模拟器 RuntimeIdentifier 与当前构建目标是否一致。
截至 2026 年 9 月 5 日,官方 dotnet/macios 发布记录已列出 .NET 10 对 Xcode 26.6 的支持版本;但“机器安装了 Xcode 26.6”并不等于“当前项目加载的 .NET for iOS 工作负载支持 Xcode 26.6”。必须以实际工作负载版本和发布记录交叉核对,而不能只凭 IDE 的安装列表判断。.NET for iOS 的 Xcode 26.6 支持记录
如果 Mac 本机最小项目已经失败,停止排查 Pair to Mac。此时 Windows 端只是把同一个工具链错误远程放大,继续删除主机记录或重装 Visual Studio 通常不会改变结果。
Pair to Mac 的连接与认证
主机发现失败
如果 Visual Studio 找不到远程 Mac,先从网络和远程登录层收集证据:
ping <MAC_HOST>
ssh -vvv -p <SSH_PORT> <MAC_USER>@<MAC_HOST>
账户、主机地址、端口和密钥均使用占位符。Mac 上需要确认“远程登录”已启用,Windows 侧则要确认连接使用的用户名与节点实际账户一致。主机发现失败、TCP 连接失败、SSH 密钥拒绝和连接后服务初始化失败,属于不同故障,不能统一处理成“重新配对”。
Pair to Mac 首次连接时会生成或使用 SSH 密钥,Visual Studio 保存的主机记录也可能保留旧账户、旧地址或不再有效的认证信息。Pair to Mac 的认证与 SSH 密钥说明
认证反复失败
如果系统反复要求认证,先做局部重建:
- 记录当前主机地址、账户、端口和 Visual Studio 中的配对状态;
- 在 Windows 上使用
ssh -vvv查看失败发生在密钥交换、账户认证还是远程命令执行; - 在 Mac 上确认目标账户可以正常登录,且账户拥有构建目录和临时目录的读写权限;
- 只删除该节点对应的旧主机记录或密钥,保留其他项目和节点的配置;
- 重新建立 Pair to Mac 连接,再用空白 MAUI iOS 项目验证;
- 只有空白项目连接和构建都通过后,才恢复业务项目测试。
删除密钥的影响是:旧连接立即失效,依赖该密钥的自动化任务也可能需要重新配置。执行前应保存节点地址、账户、端口和恢复所需的授权信息;不要在没有备份和交接记录的情况下清空全部 SSH 配置。
| 现象 | 不应直接做的事 | 应先做的验证 |
|---|---|---|
| 找不到远程 Mac | 重装全部工作负载 | DNS、地址、端口、远程登录 |
| 反复要求认证 | 清空所有密钥 | ssh -vvv、用户名、密钥权限 |
| 已连接但服务初始化失败 | 立刻重建节点 | Mac 本机最小构建、远程目录权限 |
| 空白项目通过、业务项目失败 | 反复重配 Pair to Mac | 项目 SDK、目标框架、缓存和自定义脚本 |
如果需要一台可独立隔离工具链的远程 Mac,RUVCLOUD 提供带完整权限的真实 Mac 访问环境;但在迁移正式流水线前,仍应先用最小项目验证连接、干净构建和签名闭环,可先查看 远程 Mac 方案 了解适合的节点形态。
Xcode 26.6 与工作负载对齐
Mac 上常见的隐性故障,是 xcode-select、DEVELOPER_DIR、IDE 设置和历史偏好文件分别指向不同的 Xcode。Microsoft 的故障排查文档列出了 Xcode 选择顺序,并建议优先使用 xcode-select --switch 或任务级 DEVELOPER_DIR,而不是依赖即将废弃的旧设置文件。.NET MAUI Xcode 选择与故障排查文档
先在同一个 Shell 环境中执行:
which dotnet
dotnet --info
xcode-select -p
xcodebuild -version
echo "$DEVELOPER_DIR"
env | grep -E 'MD_APPLE_SDK_ROOT|DEVELOPER_DIR'
如果需要同时保留多个 Xcode,不建议在全局环境中频繁切换。更稳妥的方式是为每个任务明确指定:
DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \
dotnet build /path/to/MinimalMauiApp.csproj \
-f net10.0-ios \
-c Debug \
-bl:/path/to/logs/xcode-26-6.binlog
若团队仍需旧版 Xcode,则把不同版本放在独立路径,并为 CI 任务写明 DEVELOPER_DIR。全局执行 xcode-select --switch 会影响同一台节点上的其他项目;如果没有任务隔离,切换一次可能让另一个流水线在下一次构建时突然加载错误的 SDK。
工作负载处理也应遵守“先确认、后修复”的顺序:
- 先保存
dotnet --info、dotnet workload list和dotnet workload --info输出; - 查看项目锁定的 SDK、
global.json、目标框架和 RuntimeIdentifier; - 将实际 .NET for iOS 版本与 官方 macios 发布记录 对照;
- 只有明确出现清单损坏、版本缺失或工作负载安装不完整时,才执行局部修复;
- 工作负载重装前,记录当前 SDK 和 NuGet 源,因为重装可能改变解析到的版本,影响其他项目。
CI 命令行与缓存隔离
Visual Studio 构建成功而 Windows 命令行失败,常见原因不是项目代码,而是两条路径使用了不同的远程 Mac、端口、账户、SDK 目录或环境变量。CI 工程师应把 IDE 和命令行的输入逐项对齐:
主机地址:<MAC_HOST>
SSH 端口:<SSH_PORT>
远程账户:<MAC_USER>
远程 SDK 目录:<REMOTE_SDK_PATH>
项目入口:/path/to/MinimalMauiApp.csproj
目标框架:net10.0-ios
配置:Debug 或 Release
Windows 侧应保存完整诊断日志和二进制日志;Mac 侧则保存 dotnet --info、Xcode 版本、工作负载清单及远程构建目录。命令行验证应从全新克隆开始,避免本地 obj、bin、NuGet 缓存或 IDE 隐式状态掩盖问题。
微软的命令行发布文档特别建议将 dotnet publish 作用于具体 MAUI 应用项目,而不是直接作用于包含多个项目类型的整个解决方案;后者可能导致每个项目被分别发布并产生额外错误。.NET MAUI iOS 命令行发布说明
缓存清理应分层执行:
- 先删除当前项目的
bin和obj; - 重新克隆项目并固定 SDK;
- 只有日志明确显示远程 SDK 或工作负载缓存损坏时,才清理节点级缓存;
- 清理后重新记录版本和路径;
- 仍然失败时,再将故障交给工具链负责人,而不是继续扩大清理范围。
节点级缓存删除可能影响同一台 Mac 上的其他项目,尤其是依赖预热 SDK、NuGet 包或构建中间产物的流水线。清理动作必须有恢复入口,例如固定版本的安装脚本、缓存重建脚本和可复现的项目锁定文件。
签名、设备与归档边界
当 Debug 或模拟器构建通过,而 Release、真机或归档失败时,故障已经越过 Pair to Mac 连接层。此时应分别检查签名身份、私钥、描述文件、Bundle Identifier、目标 RuntimeIdentifier、钥匙串访问上下文以及远程设备可见性。
建议按下面的顺序缩小范围:
- 先执行不签名的 Debug 编译,确认代码和工具链仍然可用;
- 再做一次最小签名实验,使用明确的占位证书名和描述文件名;
- 检查证书是否包含对应私钥;
- 检查描述文件中的 App ID、设备和能力是否与项目一致;
- 确认 Release 使用的
CodesignKey与CodesignProvision是实际名称,而不是显示名称或旧配置; - 最后再恢复正式归档和发布任务。
证书、私钥和描述文件必须形成匹配关系;如果私钥丢失或证书被撤销,继续重装工作负载并不能修复签名链路。签名排查应记录钥匙串访问账户、构建进程上下文和证书有效期,而不是只查看证书名称。
命令行归档可以使用占位参数验证调用结构:
dotnet publish /path/to/MauiApp.csproj \
-f net10.0-ios \
-c Release \
-p:ArchiveOnBuild=true \
-p:RuntimeIdentifier=ios-arm64 \
-p:CodesignKey="<SIGNING_CERTIFICATE>" \
-p:CodesignProvision="<PROVISIONING_PROFILE>"
官方发布流程说明,发布操作会完成构建与签名,并生成 .ipa;因此“编译通过”与“归档可发布”必须分别验收。.NET MAUI iOS 命令行归档与签名说明
平台负责人的处置决策
最后不要用“是否能成功构建”一个指标决定节点命运,而应汇总同一远程 Mac 上的多组结果:
- Mac 本机最小项目是否通过;
- Windows 远程最小项目是否通过;
- 业务项目 Debug 与 Release 是否一致;
- 模拟器构建与真机部署是否一致;
- 正式签名和归档是否通过;
- 节点重启后 Pair to Mac 是否能恢复;
- 清理缓存后是否仍能复现;
- 多个项目是否出现相同错误。
可以按下面的规则处理:
| 证据组合 | 建议动作 | 停止条件 |
|---|---|---|
| Mac 本机失败,且版本不匹配 | 修复工作负载或 Xcode | 最小项目本机通过前,不恢复正式流水线 |
| Mac 本机通过,Windows 远程失败 | 保留节点,修复 Pair to Mac、账户或远程 SDK | 空白项目远程通过后再测业务项目 |
| Debug 通过,Release 或签名失败 | 隔离证书、描述文件和钥匙串 | 最小签名实验通过前,不重装工具链 |
| 多项目持续出现版本漂移 | 双版本隔离或重建节点 | 新节点完成干净构建、重启恢复和签名验收 |
| 节点重启后状态丢失 | 修复启动脚本、路径和环境变量 | 连续完成重连与真实发布任务 |
如果只是单项目的 obj 或项目属性异常,不应重建整台 Mac;如果多个项目在相同节点上轮流出现 Xcode、工作负载和远程 SDK 漂移,且无法通过锁定路径和版本复现,才有理由建立隔离节点。
对于无法稳定隔离 Xcode 与 .NET 工作负载的现有 Mac,完成最小项目复测后,可以评估 RUVCLOUD 的独立远程 Mac 作为试验节点。相比继续把 Windows 开发机、共享 Mac 和不固定的本地 Xcode 混在一起,独立节点更容易保留 root 权限、固定工具链、重启验证和干净构建记录;但长期稳定的高负载流水线、必须接入本地物理设备或需要专用硬件接口的团队,仍应先评估自购 Mac 或自有机房方案。需要比较租赁周期与节点用途时,可参考 RUVCLOUD 的方案与价格页面。
迁移前至少完成一次 Pair to Mac 连接、一次 Mac 本机干净构建、一次 Windows 远程构建、一次 Release 签名归档,以及一次重启后的恢复验证。只要其中一项仍依赖人工改路径、临时删密钥或未记录的缓存状态,就不应把该节点直接接入正式发布流水线。