这篇只讲一件事:Flutter iOS CI/CD(主关键词锚点:Flutter iOS build on Mac mini)——如何在 Mac mini M4 上稳定跑 flutter build ipa,并把 CocoaPods 缓存、self-hosted macOS Runner 与 Linux 侧的 Android/测试 job 拆开。Flutter 项目真正的瓶颈往往不是 Dart,而是 iOS 构建链路的集中化依赖:pod install → xcodebuild archive → 签名 → 上传 TestFlight,整条链只能落在 Mac 上。我们组是典型跨平台构建分工:Android 在 Windows/Linux CI 上编,Flutter iOS CI 固定在机房一台独享 Mac mini 上跑。若你在做原生 Xcode 混合编译,可对照 告别 Xcode 卡顿;Runner 注册与 TCO 见 自建 macOS Runner。
1)为什么 Flutter iOS CI 必须单独占一台 Mac
「一套代码」解决的是业务逻辑复用,不是构建平面复用。iOS 产物仍走完整 Xcode 链路,无法在 ubuntu-latest 上完成 Flutter iOS CI;GitHub 托管 macOS 分钟贵、队列飘,且每次 job 清缓存时 flutter build ipa 耗时会回到 15–25 分钟档。把 Flutter iOS CI/CD 放到持久化环境的 Mac mini 上,核心价值是:锁 Flutter/Xcode 版本、三层缓存可复用、7×24 不合盖睡眠——团队里 Android 与 iOS 发版节奏可以真正解耦。
flutter test、build apk 留在 Linux;Flutter iOS CI 专机只跑 iOS 集成构建与签名相关步骤。2)Flutter iOS CI 云端架构图
下面是我们线上在用的 Flutter iOS CI/CD 拓扑:开发者不必须本地有 Mac,iOS 包由 self-hosted Runner 产出。
Flutter iOS CI 云端架构
本地:Dart · Android · git push
ubuntu-latestflutter test · build apk
labels: macos, flutter-ios
fvm flutter build ipapod install · xcodebuild archive
Mac mini 上持久化缓存层(Flutter iOS CI 提速关键)
~/.pub-cache- CocoaPods cache
- DerivedData
3)Flutter iOS CI 三种落地模式
| 模式 | 本地 / 其他 CI | 云端 M4 Mac | 适合 |
|---|---|---|---|
| A. 只把 iOS 构建放云上 | 日常 flutter run、Android 打包 | build ipa、上传商店 | 混合主力机(Win/Linux + 手机真机) |
| B. 全远程 Flutter(少见) | 仅编辑器 + git | 模拟器、flutter doctor 全绿 | 无本地 Mac,能接受 VNC 延迟 |
| C. 云 Mac = iOS Runner | PR 触发 Linux job | 标签 macos 的 self-hosted Runner | 发版频繁、要强缓存 |
我们日常是 A + C:合并后 Linux 跑 flutter test;Flutter iOS CI 仅在 tag / main 合并时由 Mac mini Runner 触发。模式 B 适合短期无 Mac 接入,长期仍建议 Runner 化。
4)Flutter iOS CI 环境清单(Mac mini 一次性装好)
- Xcode + CLT: 与
ios/Podfile、Flutter 通道要求的最低版本对齐;装完跑sudo xcodebuild -license accept。 - Flutter SDK: 用
fvm或固定目录~/flutter,在仓库里写.fvmrc,避免同事和 CI 各用一版。 - CocoaPods:
gem install cocoapods或 Homebrew;大仓建议锁Podfile.lock并提交。 - 签名: 团队用 Fastlane Match 或 Xcode 自动签名;p12 与描述文件不要进 Git,放加密仓库或 CI Secret。
- 网络: 节点尽量靠近 Git 远程与 pub.dev/CDN;公司内网 Git 需 VPN 或专线,见 帮助中心 的出口说明。
第一次在新机器上跑 flutter doctor -v,把输出贴进团队 Wiki——以后排障先对照这份「黄金快照」。
5)CocoaPods 缓存优化:Flutter iOS CI 提速核心(含 Before / After)
同一套中等体量 Flutter 仓(约 40+ plugins、Release IPA),我们在三种环境下各跑 3 次取中位数——Flutter iOS CI 的差异几乎全在「缓存是否留在 Mac mini 上」。
| 环境 | 首次构建(冷启动) | 二次构建(缓存命中) |
|---|---|---|
| 本地 MacBook Air M2(8GB,合盖/降频) | 18–25 min | 12–15 min |
GitHub 托管 macos-latest(无自定义缓存) | 16–22 min | 14–18 min |
| Mac mini M4(16GB)· Flutter iOS CI + 三层缓存 | 15–20 min | 4–8 min |
二次构建从 12+ 分钟压到个位数,靠的是架构图里那三层持久化——这也是我们把 Flutter iOS build on Mac mini 从「能编」升级到「能当 CI」的分水岭。
PUB_CACHE/~/.pub-cache: Runner 环境变量写死路径,job 间复用 Dart 依赖。- CocoaPods cache +
ios/Pods:Podfile.lock未变则跳过pod install;变则只增量解析。 - DerivedData: 固定
~/DerivedData,除非升级 Xcode 大版本否则不clean。
6)Flutter iOS CI 命令流(SSH 或 Runner 脚本)
在仓库根目录,版本号请换成你项目锁定的 Flutter:
cd ~/work/my_flutter_app
export PUB_CACHE=$HOME/.pub-cache
fvm flutter pub get
cd ios && pod install --repo-update && cd ..
fvm flutter build ipa --release \
--export-options-plist=ios/ExportOptions.plist
在 GitHub Actions 里,把上述步骤写进 release-ios.yml,runs-on: [self-hosted, macos, flutter-ios] 即可。上传 TestFlight 时 Mac mini 出口带宽与 App Store Connect 路由,往往比开发者家里 Wi‑Fi 更稳定。
7)GitHub Actions:Flutter iOS CI/CD workflow 拆分
推荐拆成两条 workflow(或 job matrix 里用 if 分流):
test-android.yml:ubuntu-latest上flutter test、build apk。release-ios.yml:runs-on: [self-hosted, macos, flutter],只在 tag 或main合并时跑build ipa+ 上传。
云 Mac 同时跑 VNC 调试和 Runner 时,给构建单独系统用户,避免「人工改文件 + 自动化检出」互相踩。并发与磁盘规划可参考 Runner TCO 一文。Windows 主力机同学的整体路径,还可看 Windows + 云端 Mac——逻辑与 Flutter 一致,只是本地没有 Xcode 而已。
8)常见问题:Flutter iOS CI 为什么卡住(how / fix)
这部分对应 Google 上最常见的 why / how / fix / error 搜索——也是我们在团队 Wiki 里维护的排障入口。
pod install 要跑 10+ 分钟,正常吗?
Why: Runner 每次在临时目录 checkout,CocoaPods 下载缓存未挂载,或 pod install --repo-update 被写进默认脚本。
Fix: 固定 WORK 路径;对比 Podfile.lock hash,未变则跳过;把 --repo-update 改成每周维护 job 手动跑。
xcodebuild archive 失败 / exit code 65
Why: Flutter 与 Xcode 版本不匹配、DerivedData 损坏、或某 native plugin 未兼容当前 SDK。
Fix: CI 开头跑 fvm flutter doctor -v 并归档日志;删 DerivedData 后重跑;对照 Xcode Release Notes 升级通道。
signing failed / 描述文件不匹配
Why: 钥匙串未导入 p12、bundle id 与描述文件不一致、Match 仓库权限过期。
Fix: Mac mini Runner 用专用钥匙串;Fastlane Match 续期;必要时 VNC 点「始终信任」——Flutter iOS CI 里这是最高频的「非代码」失败。
DerivedData 暴涨占满磁盘
Why: 多分支并行 job、旧 Archives 未清、多 Flutter 版本共存。
Fix: cron 清 ~/Library/Developer/Xcode/Archives;Runner 并发设为 1–2;磁盘建议 ≥512GB 或定期扩容。
Flutter 版本不一致导致插件编译错误
Why: 开发者本地未用 fvm,CI 用了另一通道。
Fix: 仓库提交 .fvmrc;CI 统一 fvm install && fvm flutter pub get;在 PR 模板里加「是否变更 Flutter SDK」勾选项。
9)什么时候该上专用 Mac mini 跑 Flutter iOS CI
| 你的情况 | 继续蹭同事 Mac / 买二手本 | 上云端 M4 Mac mini |
|---|---|---|
| 每月 iOS 发版 ≥ 4 次 | 协调成本高 | 建议 固定构建机 + 缓存 |
| 团队无 Mac,仅 Flutter | 难持续 | 日租试跑 一轮真实 build ipa |
| 已有 Mac mini 放办公室 7×24 | 自建即可 | 云 Mac 作灾备或峰值扩容 |
| 大量 iOS 插件需调原生 | 本地 Mac 仍方便 | 云机编 release,本地编 debug |
10)FAQ(Flutter iOS CI/CD)
Flutter iOS CI 和原生 iOS CI 有何不同?
多一层 flutter pub get 与 plugin 代码生成,且版本必须与工作区 .fvmrc 一致;Xcode 侧仍是 Pods + archive。
和 Codemagic / 托管 macOS 比?
托管适合快速验证;当二次构建要稳定在 10 分钟内、且要强缓存时,Flutter iOS build on Mac mini + self-hosted Runner 更可控。
第一次跑通 Flutter iOS CI 要多久?
熟手约 1 个工作日:装 Xcode/Flutter、注册 Runner、配签名、跑通一条 build ipa 并上传 TestFlight。
工程结论:Flutter iOS CI 最小可行方案(MVF)
若你的 Flutter 项目每周至少发布一次 iOS 包,且团队存在 Windows/Linux 主力机,那么可落地的最小方案是:
- 1 台 16GB Mac mini M4(物理机或独享云主机均可)专职 Flutter iOS CI;
- 注册 self-hosted macOS Runner,与 Linux 侧
flutter test/build apk分 workflow; - 持久化
.pub-cache、CocoaPods cache、DerivedData 三层缓存; - 目标:48 小时内用真实仓库跑通第一条
flutter build ipa→ TestFlight,再决定是否加第二台做并发或灾备。
我们用同一套 MVF 把二次构建压进 4–8 分钟 档(见第 5 节对比表)。需要独享 Mac mini 节点与 SSH 交付时,可参考 定价方案 与 帮助中心 对照自己的发版频率算账——先验证 pipeline,再谈规模化。