首页
/ Flutter 仓库 iOS 开发签名证书年度续期:mobileprovision CIPD 包的构建、打标签与 CI 版本迁移全流程

Flutter 仓库 iOS 开发签名证书年度续期:mobileprovision CIPD 包的构建、打标签与 CI 版本迁移全流程

2026-09-07 22:34:01作者:牧宁李

mobileprovision 是 Flutter 框架仓库中一套面向 iOS 真机测试基础设施的运维级流程:每年 iOS development signing certificate 到期后,需要把新签发的 Provisioning Profile 打成 flutter_internal/mac/mobileprovision 系列 CIPD 包,并完成 ref 指向与年度 tag 迁移。本文以仓库内 mobileprovision/README.md 为主线,结合目录中的两份 CIPD 包定义文件,完整还原这一年度续期操作的 14 个步骤,并逐条解读 cipd create / set-ref / set-tag 命令的语义与包内配置字段,帮助读者掌握这套面向 devicelab 等 CI bot 环境的签名凭据分发与版本管理方案。

一、背景:为什么 iOS 签名证书每年都需要一次“打补丁式”续期

Flutter 仓库使用 devicelab 与基于 Chrome 的测试框架(chrome bots)在物理 iOS 测试设备上运行真实应用测试。要让这些应用被设备信任并安装,需要用团队持有的 iOS development signing certificate 为应用签名,并配套一个 Provisioning Profile(描述文件)。

这里的核心约束是证书会过期,且续期有过渡窗口

  • iOS development signing certificate 大约每年到期一次,到期后必须由证书管理员签发新的证书;
  • 新证书签发后,物理测试设备上新旧两代证书都需要保持可用,因为测试机上可能还残留着用旧证书签名的历史安装包;
  • 因此需要生成一个新的 Provisioning Profile,使同时由旧证书和新证书签名的应用都能在这些真机上运行。

需要说明的是:完整续期还包含“签发新证书”等前置与后续环节,这部分流程记录在 Google 内部文档中;本 README(engine/src/flutter/tools/cipd/mobileprovision/README.md)专门负责其中“把新 Profile 制作成可分发、可追溯的 CIPD 包”这一个子步骤,并与 CI 配置中的 apple_signing 依赖版本联动。

二、包结构:mobileprovision 目录里有什么

在仓库的 tools/cipd 下,CIPD 相关工具与包定义按主题分目录存放(同级的还有 android_embedding_bundle/malioc/ 等)。其中 mobileprovision/ 目录结构如下:

engine/src/flutter/tools/cipd/mobileprovision/
├── README.md        # 年度更新操作手册(本文主题)
├── mac-arm64.yaml   # Apple Silicon Mac 测试机对应的包定义
└── mac-amd64.yaml   # Intel Mac 测试机对应的包定义

为什么拆成两个包? 因为 devicelab / chrome bots 中的 macOS 测试宿主机既有 Apple Silicon(arm64)也有 Intel(amd64)两种架构,CI 在不同架构机器上以不同包名拉取,CIPD 的架构隔离能让两端互不干扰地独立发布与回滚。

两份 YAML 内容几乎一致,唯一的区别是 package 字段的后缀。以 mac-arm64.yaml 为例,完整定义如下:

# Comments are allowed.

# The package name is required. Third-party chromium dependencies should
# unsurprisingly all be prefixed with chromium/third_party/.
package: flutter_internal/mac/mobileprovision/mac-arm64

# The description is optional and is solely for the reader's benefit. It
# isn't used in creating the CIPD package.
description: iOS provisioning provide for "match Development" signing certificate

# The root is optional and, if unspecified, defaults to ".". It specifies the
# root directory of the files and directories specified below in "data".
#
# You won't typically need to specify this explicitly.
root: "."

# The install mode is optional. If provided, it specifies how CIPD should
# install a package: "copy", which will copy the contents of the package
# to the installation directory; and "symlink", which will create symlinks
# to the contents of the package in the CIPD root inside the installation
# directory.
#
# You won't typically need to specify this explicitly.
install_mode: "copy"

# The data is required and described what should be included in the CIPD
# package.
data:
  - file: development.mobileprovision

YAML 字段逐一拆解

字段 必填 本仓库取值 语义说明
package flutter_internal/mac/mobileprovision/mac-arm64(或 .../mac-amd64 CIPD 包全局唯一名。命名参考了 Chromium 系依赖统一使用 flutter_internal/ 前缀的惯例;这里的 mobileprovision 即“iOS 描述文件”
description iOS provisioning provide for "match Development" signing certificate 仅给人看的说明文字,不参与建包。点明该 profile 是配合 match Development 方式管理的 development 签名证书使用
root "." 指定 data 中相对路径的基准目录,缺省就是当前目录,此处显式写 "." 通常无需改动
install_mode "copy" 安装方式二选一:copy 把包内容复制到安装目录;symlink 则在安装目录内的 CIPD root 下创建符号链接。这里用 copy 保证 bot 环境下拿到的是真实文件
data - file: development.mobileprovision 声明打进包里的内容,即被更新的描述文件本体,文件名固定为 development.mobileprovision

从源码结构可以推断:CI 环境准备阶段会通过某种 cipd ensure/依赖拉取逻辑按 apple_signing 依赖读取该包,而包内始终只装一个固定文件名的 development.mobileprovision,因此每次更新只是“换文件、重新建包、移动版本指针”。

三、年度更新操作全流程(14 步)

下面是 README 定义的完整操作序列,按“前置授权 → 建包上传 → 移动 ref → 打年度 tag → CI 迁移”五个阶段重新梳理。

阶段 0:前置条件

Step 1–2:获取并等待 CIPD 写权限。 操作者需要先申请对相关 CIPD 包的读写访问权(Flutter 的 LUCI CIPD 实例管理页面中完成),随后等待约 5 分钟让权限系统同步生效。

说明:完整续期还包含“向 Apple 申请新证书”等其他环节,具体见仓库 README 中指向的 Google 内部流程文档;此处只执行与 mobileprovision 包相关的那一段。

阶段 1:更新描述文件并创建两个架构的 CIPD 包

Step 3:替换描述文件。 把新签发的 iOS Provisioning Profile 拷贝到本目录,并固定命名为 development.mobileprovision。文件名必须与两个 YAML 中 data: - file: 的声明严格一致,否则建包时 cipd create 会因找不到文件而失败。

# 在 engine/src/flutter/tools/cipd/mobileprovision/ 目录下执行
cp /path/to/new/profile.mobileprovision ./development.mobileprovision

Step 4:为 arm64 架构创建包。

cipd create --pkg-def mac-arm64.yaml

--pkg-def 会让 CIPD 按 mac-arm64.yaml 中的 package + data 声明收集文件并上传,生成一个新的不可变实例(instance)。

Step 5:验证并记录 arm64 实例 ID。 打开 CIPD 包管理页面中 flutter_internal/mac/mobileprovision/mac-arm64 包的最新一次上传记录,复制页面上的 Instance_ID(即这次上传对应的唯一实例 ID,后续 ref/tag 操作都指向它)。这一步是保证后续命令可追溯的关键,务必先复制再继续。

Step 6:为 amd64 架构创建包。

cipd create --pkg-def mac-amd64.yaml

Step 7–9:验证并记录 amd64 实例 ID。 同样在 flutter_internal/mac/mobileprovision/mac-amd64 页面找到最新上传,复制其 Instance_ID

阶段 2:把 latest ref 指向最新实例

latest 是包的一个移动指针(ref),CI 默认按 latest 或版本 tag 取包,因此必须先把 ref 拨到新实例,旧实例仍保留在历史中可回滚。

Step 10:arm64 的 latest 指向新实例。 用上面复制的 ARM64_INSTANCE_ID 替换命令中的占位符:

cipd set-ref flutter_internal/mac/mobileprovision/mac-arm64 -ref latest -version ARM64_INSTANCE_ID

Step 11:amd64 的 latest 指向新实例:

cipd set-ref flutter_internal/mac/mobileprovision/mac-amd64 -ref latest -version AMD64_INSTANCE_ID

语义提示:set-ref 相当于给实例“改名贴标”,-version 既可以是实例 ID,也可以是其他 ref 名。只用实例 ID 指向是最严谨的写法。

阶段 3:为新实例打年度版本 tag

为了后续能在 .ci.yaml 里稳定引用“某一年的证书方案”,惯例是给当年的两个实例打上 version:to_<年份> 格式的 tag(例如 2025 年即为 version:to_2025)。

Step 12:给 arm64 实例打 tag。YOUR_NEW_TAG 换成 version:to_2025(或对应年份):

cipd set-tag flutter_internal/mac/mobileprovision/mac-arm64 -tag YOUR_NEW_TAG -version ARM64_INSTANCE_ID

Step 13:给 amd64 实例打同样的 tag:

cipd set-tag flutter_internal/mac/mobileprovision/mac-amd64 -tag YOUR_NEW_TAG -version AMD64_INSTANCE_ID

阶段 4:在 CI 配置中完成版本迁移

Step 14:更新 .ci.yaml 找到 CI 工作流中所有 apple_signing 依赖步骤,把 version 从上一年的 tag 改为今年新打的 tag:

# 迁移前
{"dependency": "apple_signing", "version": "version:to_2024"}

# 迁移后
{"dependency": "apple_signing", "version": "version:to_2025"}

这一步的作用是让后续 CI 在准备签名环境时显式使用新一年的 profile,而不是依赖会随时间变化的 latest 指针,从而保证历史任务可复现(回看旧提交时仍能拉到与之匹配的旧 profile)。

四、命令速查表

阶段 命令 作用
建包 cipd create --pkg-def mac-arm64.yaml 依 YAML 声明打包上传 development.mobileprovision(arm64)
建包 cipd create --pkg-def mac-amd64.yaml 同上(amd64)
移动指针 cipd set-ref <pkg> -ref latest -version <INSTANCE_ID> latest ref 指向本次上传的新实例
打 tag cipd set-tag <pkg> -tag version:to_2025 -version <INSTANCE_ID> 用年度 tag 标记新实例,供 .ci.yaml 稳定引用
CI 迁移 修改 .ci.yamlapple_signingversion 让后续 CI 使用新年度 profile

五、实操注意事项与排障要点

综合 README 流程与包定义文件,可以总结出以下容易踩坑的点:

  1. 文件命名必须精确匹配。 包体内只收录 development.mobileprovision 一个文件,两个 YAML 的 data 都指向该固定文件名;拷贝错误或大小写不一致都会导致 cipd create 失败。
  2. Instance ID 随手记录。 建包完成后网页端展示的 Instance_ID 是唯一不可变标识,后续 4 条 ref/tag 命令都依赖它;建议按 arm64 / amd64 分两组保管,避免混用。
  3. 两个架构都要操作,不可只更一个。 建包、设 latest、打 tag 都必须对 mac-arm64mac-amd64 各执行一遍,否则会在某类 Mac 测试宿主机上出现签名凭据未更新的现象。
  4. tag 迁移需要与建包同批完成。 若先更新了 .ci.yaml 而 tag 尚未就绪,CI 解析 version:to_2025 会失败;建议严格按“先建包打 tag、再改 .ci.yaml”的顺序推进。
  5. 权限同步有延迟。 新申请写权限后需要等待约 5 分钟再执行 cipd create,否则可能被服务端拒绝。

六、从仓库继续深入

通过以上流程,读者即可理解 Flutter 仓库如何在“证书年度到期”这一周期性约束下,用 CIPD 的 instance / ref / tag 三层模型实现 iOS 签名描述文件的平滑换发,并保证 CI 上所有历史任务依然可复现、可追溯。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391