← 返回技术博客

Flutter iOS CI/CD:在 Mac mini M4 上构建 IPA 与 CocoaPods 缓存优化实战

Flutter iOS CI/CD:Mac mini M4 上 flutter build ipa 与 self-hosted Runner 流水线
Flutter iOS CI/CD 典型分工:Windows/Linux 写 Dart,Mac mini M4 Runner 出 IPA。

这篇只讲一件事: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 installxcodebuild 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 testbuild apk 留在 Linux;Flutter iOS CI 专机只跑 iOS 集成构建与签名相关步骤。

2)Flutter iOS CI 云端架构图

下面是我们线上在用的 Flutter iOS CI/CD 拓扑:开发者不必须本地有 Mac,iOS 包由 self-hosted Runner 产出。

Flutter iOS CI 云端架构

Developer(Windows / Linux / Mac)
本地:Dart · Android · git push
GitHub Actions · ubuntu-latest
flutter test · build apk
Mac mini M4 · self-hosted Runner
labels: macos, flutter-ios
fvm flutter build ipa
pod install · xcodebuild archive
TestFlight / App Store Connect

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 RunnerPR 触发 Linux job标签 macos 的 self-hosted Runner发版频繁、要强缓存

我们日常是 A + C:合并后 Linux 跑 flutter testFlutter iOS CI 仅在 tag / main 合并时由 Mac mini Runner 触发。模式 B 适合短期无 Mac 接入,长期仍建议 Runner 化。

4)Flutter iOS CI 环境清单(Mac mini 一次性装好)

  1. Xcode + CLT:ios/Podfile、Flutter 通道要求的最低版本对齐;装完跑 sudo xcodebuild -license accept
  2. Flutter SDK:fvm 或固定目录 ~/flutter,在仓库里写 .fvmrc,避免同事和 CI 各用一版。
  3. CocoaPods: gem install cocoapods 或 Homebrew;大仓建议锁 Podfile.lock 并提交。
  4. 签名: 团队用 Fastlane Match 或 Xcode 自动签名;p12 与描述文件不要进 Git,放加密仓库或 CI Secret。
  5. 网络: 节点尽量靠近 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 上」。

表:Flutter iOS CI 全量 build ipa 耗时(分钟,同一仓库 · 2026-05 实测)
环境首次构建(冷启动)二次构建(缓存命中)
本地 MacBook Air M2(8GB,合盖/降频)18–25 min12–15 min
GitHub 托管 macos-latest(无自定义缓存)16–22 min14–18 min
Mac mini M4(16GB)· Flutter iOS CI + 三层缓存15–20 min4–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
量化结论: 若你们每周 ≥1 次 iOS 发版,Flutter iOS CI 在 Mac mini 上命中缓存后,单次 pipeline 可节省约 8–12 分钟;月 4 次发版、3 名开发者等待,就是一整 WORKDAY 量级的排队时间。

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.ymlruns-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-latestflutter testbuild 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. 1 台 16GB Mac mini M4(物理机或独享云主机均可)专职 Flutter iOS CI
  2. 注册 self-hosted macOS Runner,与 Linux 侧 flutter test / build apk 分 workflow;
  3. 持久化 .pub-cache、CocoaPods cache、DerivedData 三层缓存;
  4. 目标:48 小时内用真实仓库跑通第一条 flutter build ipa → TestFlight,再决定是否加第二台做并发或灾备。

我们用同一套 MVF 把二次构建压进 4–8 分钟 档(见第 5 节对比表)。需要独享 Mac mini 节点与 SSH 交付时,可参考 定价方案帮助中心 对照自己的发版频率算账——先验证 pipeline,再谈规模化。