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、远程服务初始化失败,或 CodesignKeyCodesignProvision 无法匹配。后续每次修复都使用同一个项目、配置、目标框架和运行目标复测,否则很容易把多个故障混在一起。

开发者的最小构建证据

应用开发者首先要确认项目实际使用的版本,而不是只看项目文件里引用的 .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 是否存在对应的 mauiios 或相关 .NET for iOS 工作负载;
  • xcode-select -p 是否指向完整的 Xcode .app,而不是只安装了命令行工具;
  • xcodebuild -version 返回的是否确实是 Xcode 26.6,而不是旧路径残留;
  • net10.0-iosios-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 密钥说明

认证反复失败

如果系统反复要求认证,先做局部重建:

  1. 记录当前主机地址、账户、端口和 Visual Studio 中的配对状态;
  2. 在 Windows 上使用 ssh -vvv 查看失败发生在密钥交换、账户认证还是远程命令执行;
  3. 在 Mac 上确认目标账户可以正常登录,且账户拥有构建目录和临时目录的读写权限;
  4. 只删除该节点对应的旧主机记录或密钥,保留其他项目和节点的配置;
  5. 重新建立 Pair to Mac 连接,再用空白 MAUI iOS 项目验证;
  6. 只有空白项目连接和构建都通过后,才恢复业务项目测试。

删除密钥的影响是:旧连接立即失效,依赖该密钥的自动化任务也可能需要重新配置。执行前应保存节点地址、账户、端口和恢复所需的授权信息;不要在没有备份和交接记录的情况下清空全部 SSH 配置。

现象 不应直接做的事 应先做的验证
找不到远程 Mac 重装全部工作负载 DNS、地址、端口、远程登录
反复要求认证 清空所有密钥 ssh -vvv、用户名、密钥权限
已连接但服务初始化失败 立刻重建节点 Mac 本机最小构建、远程目录权限
空白项目通过、业务项目失败 反复重配 Pair to Mac 项目 SDK、目标框架、缓存和自定义脚本

如果需要一台可独立隔离工具链的远程 Mac,RUVCLOUD 提供带完整权限的真实 Mac 访问环境;但在迁移正式流水线前,仍应先用最小项目验证连接、干净构建和签名闭环,可先查看 远程 Mac 方案 了解适合的节点形态。

Xcode 26.6 与工作负载对齐

Mac 上常见的隐性故障,是 xcode-selectDEVELOPER_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 --infodotnet workload listdotnet 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 版本、工作负载清单及远程构建目录。命令行验证应从全新克隆开始,避免本地 objbin、NuGet 缓存或 IDE 隐式状态掩盖问题。

微软的命令行发布文档特别建议将 dotnet publish 作用于具体 MAUI 应用项目,而不是直接作用于包含多个项目类型的整个解决方案;后者可能导致每个项目被分别发布并产生额外错误。.NET MAUI iOS 命令行发布说明

缓存清理应分层执行:

  1. 先删除当前项目的 binobj
  2. 重新克隆项目并固定 SDK;
  3. 只有日志明确显示远程 SDK 或工作负载缓存损坏时,才清理节点级缓存;
  4. 清理后重新记录版本和路径;
  5. 仍然失败时,再将故障交给工具链负责人,而不是继续扩大清理范围。

节点级缓存删除可能影响同一台 Mac 上的其他项目,尤其是依赖预热 SDK、NuGet 包或构建中间产物的流水线。清理动作必须有恢复入口,例如固定版本的安装脚本、缓存重建脚本和可复现的项目锁定文件。

签名、设备与归档边界

当 Debug 或模拟器构建通过,而 Release、真机或归档失败时,故障已经越过 Pair to Mac 连接层。此时应分别检查签名身份、私钥、描述文件、Bundle Identifier、目标 RuntimeIdentifier、钥匙串访问上下文以及远程设备可见性。

建议按下面的顺序缩小范围:

  • 先执行不签名的 Debug 编译,确认代码和工具链仍然可用;
  • 再做一次最小签名实验,使用明确的占位证书名和描述文件名;
  • 检查证书是否包含对应私钥;
  • 检查描述文件中的 App ID、设备和能力是否与项目一致;
  • 确认 Release 使用的 CodesignKeyCodesignProvision 是实际名称,而不是显示名称或旧配置;
  • 最后再恢复正式归档和发布任务。

证书、私钥和描述文件必须形成匹配关系;如果私钥丢失或证书被撤销,继续重装工作负载并不能修复签名链路。签名排查应记录钥匙串访问账户、构建进程上下文和证书有效期,而不是只查看证书名称。

命令行归档可以使用占位参数验证调用结构:

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 签名归档,以及一次重启后的恢复验证。只要其中一项仍依赖人工改路径、临时删密钥或未记录的缓存状态,就不应把该节点直接接入正式发布流水线。