Appearance
CI/CD 流水线与 Fastlane 自动化分发
回到总览:构建与发布工程相关模块:iOS 构建与依赖 · Gradle 构建生命周期 · Flutter 产物瘦身与符号化
一句话定义
CI/CD 把「提交 → 检查 → 构建 → 测试 → 签名打包 → 分发/发布」固化为可重复流水线;Fastlane 用 lane 把签名、截图、上传、TestFlight/商店提交的胶水步骤脚本化,消除「本地能出包、CI 出不了」的环境差。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| Fastlane 边界收口样板 | flutter-host-fastlane-boundary-demo | Fastfile |
为什么需要
- 一条 CI 流水线从提交到上线的关键环节有哪些?
- 一句话答:触发 → 检查(lint/静态分析) → 构建 → 测试 → 签名打包 → 分发/发布;每环失败即阻断(详见 §底层机制.1)。
- Fastlane 主要解决哪类问题?
- 一句话答:把签名、打包、上传、TestFlight/商店提交等胶水步骤固化为可重复
lane,统一本地与 CI 环境(见 §底层机制.2)。
- 一句话答:把签名、打包、上传、TestFlight/商店提交等胶水步骤固化为可重复
- 为什么发布构建必须归档符号表(dSYM / mapping)?
- 一句话答:线上崩溃堆栈是地址,需 dSYM(iOS) / mapping.txt(Android) / .symbols(Flutter) 才能符号化还原(呼应 07/10 瘦身页)。
底层机制
1. 流水线阶段(以 GitHub Actions 为例)
yaml
# .github/workflows/release.yml(节选)
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: bundle exec fastlane beta # 调用 Fastlane lane- 可能输出
Run bundle exec fastlane beta [12:01:02]: fastlane finished with exit code 0 [12:01:03]: Uploaded to TestFlight 🚀 - 预期现象:PR 触发后依次跑检查/构建/测试,任一步非零退出即流水线标红阻断;
fastlane beta在末尾把包上传到 TestFlight。 - 观察重点:
runs-on的镜像(如 macOS 版本)决定 Xcode 版本,是 iOS 构建“挑机器”的源头;用actions/cache缓存依赖可显著提速。
2. Fastlane lane 把胶水步骤串起来
ruby
# Fastfile
lane :beta do
build_app(scheme: "MyApp") # 等价于 gym,内部调 xcodebuild archive
upload_to_testflight(skip_waiting_for_build_processing: true)
end- 概念:
build_app封装xcodebuild archive+ 导出;upload_to_testflight处理登录、上传、元数据。Android 侧对应gradle(task: "assembleRelease")+upload_to_play_store。
3. 当前仓库为什么不应该为了 Fastlane 新造一套 iOS host
这次任务里最容易“看起来很完整、实际上维护成本很高”的做法,就是为了证明 Fastlane 能用,临时再造一套独立 labs/ios/host。这不符合当前仓库的 host 结构,也会引入无法在非 macOS/Xcode 环境稳定维护的伪工程。
当前更诚实、也更可维护的收口是:
- 复用现有
labs/flutter/host的 iOS Runner 与 Android app。 - 在
labs/ci/flutter-host-fastlane-boundary-demo/Fastfile里只演示 lane 如何统一调 Flutter host。 - iOS lane 明确停在
flutter build ios --simulator --debug --no-codesign,只验证“Fastlane 能否统一调度构建命令”。 - 把签名、archive、export、TestFlight 上传明确声明为“需要真实 macOS + Xcode + 证书环境”的外部边界。
ruby
lane :host_ios_build_dry_run do
sh("flutter --version")
sh("flutter pub get", chdir: "../../flutter/host")
sh("flutter build ios --simulator --debug --no-codesign", chdir: "../../flutter/host")
end- 可能输出text
$ fastlane ios host_ios_build_dry_run $ flutter pub get $ flutter build ios --simulator --debug --no-codesign - 预期现象:在具备 Flutter+iOS 工具链的 macOS 机器上,可以成功走通“Fastlane → Flutter host iOS debug/simulator 构建”这一层。
- 观察重点:这条 lane 证明的是 Fastlane 的胶水价值,不是证明当前仓库已经具备真实发布到 TestFlight 的完整环境。
4. 签名物料如何安全注入
- 证书/keystore/密钥绝不进仓库,存 CI Secret 或专用签名服务(如 fastlane
match走加密 Git 仓库)。 - 构建机临时从 Secret 还原签名物料,构建完即清理,降低泄露面。
Android / Flutter / Web / Backend 对照
| 环节 | 移动端 CI | Web CI | 通用 |
|---|---|---|---|
| 触发 | push/PR/tag | 同 | webhook |
| 构建 | Gradle / xcodebuild / flutter build | vite build / webpack | 语言构建 |
| 签名分发 | TestFlight / Play / 蒲公英 | CDN / OSS | 制品库 |
| 胶水自动化 | Fastlane | 各平台 CLI / 脚本 | 脚本 |
常见场景
- PR 卡点:每次 PR 跑 lint + 单测,拦截低级错误。
- Nightly / Release 分支发布:定时或打 tag 触发
beta/releaselane。 - 多环境:
lane :staging与lane :production共用打包、切不同签名与后端地址。
常见误配、事故后果与排障
1. 事故:CI 缓存未命中,构建时间翻倍
- 误配原因:依赖缓存 key 设计不当或
actions/cache路径错。 - 后果:PR 排队变长,开发者等待。
- 排障与修法:按 lock 文件内容做 cache key;确认缓存路径覆盖
Pods/node_modules/ Gradle 缓存。
2. 误配:签名密钥硬编码进仓库或日志泄露
- 后果:攻击者可用你的身份发布恶意包。
- 排障与修法:密钥只放 CI Secret / 签名服务;构建日志屏蔽敏感变量;定期轮换。
3. 事故:本地能出包、CI 不能(环境差)
- 误配原因:本地 Xcode/Gradle 版本、签名物料与 CI runner 不一致。
- 后果:发布阻塞在最后一刻。
- 排障与修法:用 Fastlane 统一 lane,让本地与 CI 走同一脚本;CI 固定 runner 镜像版本。
4. 误配:把 Fastlane demo 写成“伪完整上线链路”
- 后果:文档里看起来有 iOS 自动发布样板,但实际没有 Xcode、签名、App Store Connect 前置条件,后续维护者反而更难判断哪部分是真的可运行。
- 排障与修法:像本仓库现在这样,把 dry-run / no-codesign / simulator 构建和真实发布链路分开写,边界明确。
相近概念对比
- CI vs CD:CI 聚焦自动构建/测试(质量门);CD 进一步自动部署/发布(交付)。
- Fastlane vs 各平台 CLI:Fastlane 用统一
lane抽象跨平台胶水;裸 CLI 更直白但重复劳动多。 - lane vs job:Fastlane 的
lane是单机内一组步骤;CI 的job是 runner 上的一次执行单元,二者常嵌套(job 里调 lane)。 - artifact vs cache:artifact 是跨 job 传递的构建产物;cache 是加速复用的中间态。
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| flutter-host-fastlane-boundary-demo | 复用现有 Flutter host,演示 Fastlane lane 如何统一调度 Android / iOS 构建命令(与文首「代码索引」一致) | Fastfile |
复习检查题
CI 流水线「检查 → 构建 → 测试 → 打包 → 发布」中,哪一环失败最该“早阻断”?为什么? 答:越靠前越该早阻断——lint/静态分析在构建前就能拦下低级错误,成本最低;若等到打包/发布才失败,已耗费构建与签名资源。
Fastlane 相比直接调
xcodebuild/gradlew的价值? 答:把签名、打包、上传、TestFlight/商店提交等易出环境差的胶水步骤抽象成可重复lane,本地与 CI 共用同一脚本,消除“本地能出包 CI 不能”。为什么发布流水线要归档 dSYM / mapping / .symbols? 答:线上崩溃堆栈是裸地址,必须靠这些符号表才能还原成源码行号(呼应 07/10 瘦身页的符号化还原)。
速记
- 六环节:触发 → 检查 → 构建 → 测试 → 签名打包 → 分发。
- Fastlane 价值:lane 统一本地/CI,专治“环境差”。
- 仓库边界:当前只落地到 Flutter host + dry-run/no-codesign,不伪造真实 TestFlight 发布。
- 签名物料:只进 Secret/签名服务,绝不进仓库。