首页
/ Flutter 基础设施自助服务指南:基于 .ci.yaml 与引擎构建配置实现 CI/发布/安全能力的自助开通

Flutter 基础设施自助服务指南:基于 .ci.yaml 与引擎构建配置实现 CI/发布/安全能力的自助开通

2026-09-06 20:02:02作者:钟日瑜

本文以 Flutter 仓库的自助服务总索引 docs/Flutter-Self-Service-Index.md 为主体,系统梳理 Flutter 项目面向贡献者提供的一系列自助服务能力:从用 .ci.yaml 声明式配置 CI 任务、用引擎构建定义文件(Engine build configurations)描述引擎构建与测试,到 FirebaseLab 真机/模拟器测试、Android 模拟器 Devicelab 测试、引擎二进制代码签名元数据、cherry-pick 与发布候选分支、以及安全相关的漏洞扫描等。读完本文后,你将知道每类自助服务面向哪类人群、在哪里配置、配置项含义是什么,并能结合仓库中的真实示例(如 .ci.yamlengine/src/flutter/ci/builders/README.md)为自己的改动选择合适的自助服务并写出正确的配置。

一、面向的人群(Audiences)

自助服务并非对所有人开放。原文档将用户分为四类,理解这些边界是正确使用各项服务的前提:

人群 说明
Flutter contributors 任何向 flutter 组织贡献代码的人,不要求是组织成员
Flutter organization members 对 flutter 组织资源具有写权限的人,参见 贡献者权限说明
Googlers 同时是 Flutter 组织成员与 Google 员工的用户
Flutter organization administrators 对组织设置具有写权限的组织成员

可以看到,基础设施类的服务(.ci.yaml、引擎构建配置、FirebaseLab、代码签名)面向所有 Flutter 贡献者,而部分服务(重跑 presubmit/postsubmit、LED 并行跑测试、SLO 指标看板、GCP 项目权限申请等)仅对 Googlers 开放。

二、基础设施服务总览

以下是原文档「Infrastructure」一节的完整服务清单(原文档中指向仓库外部的文档链接,凡在仓库内有对应文件的,本文已替换为仓库内相对路径):

服务 说明 人群 配置位置
.ci.yaml 告知 Flutter 基础设施在给定仓库中用哪些任务验证提交的配置文件,从每个受支持仓库的顶层目录读取 Flutter contributors 仓库顶层目录,例如本仓库的 .ci.yaml
Engine build configurations 描述 Flutter 引擎构建与测试的配置文件 Flutter contributors engine/src/flutter/ci/builders 目录,文档见该目录下的 README
Autosubmit 对满足审批条件的 PR 自动提交的 GitHub 应用 组织管理员 应用代码位于 flutter/cocoon 仓库(仓库外,此处仅说明位置)
FirebaseLab tests 通过 .ci.yaml 配置、使用 FirebaseLab 资源的特殊测试 Flutter contributors 直接写在 .ci.yaml 中,文档见 FirebaseLab 测试指南
Codesigning 为引擎产物添加供签名基础设施使用的元数据 Flutter contributors 引擎目录中的 GN 文件与全局生成器脚本,文档见 代码签名元数据指南
Emulators support 从测试中使用 Android 模拟器 Flutter contributors 指南见 在 Devicelab 模拟器上测试 Android 改动
Rerun GitHub presubmit test(命令行) 通过 reset-try-task 端点与 gcloud CLI 重跑 presubmit 任务 Googlers 源码位于 flutter/cocoon 仓库的 app_dart/lib/src/request_handlers/reset_try_task.dart(仓库外)
Rerun postsubmit test(构建看板) 从 Flutter 构建看板重跑 postsubmit 任务 Googlers 内部看板(Googler 内部资源)
Run a test multiple times in parallel via LED 对 PR 并行多次运行分片测试以验证改动/修复 Googlers 内部 playbook(Googler 内部资源)
Create a CIPD package 创建 CIPD 包并将构建脚本加入 cocoon,实现自动构建与上传到 flutter CIPD 命名空间 Flutter contributors 构建脚本位于 flutter/cocoon 仓库的 cipd_packages 目录(仓库外);公共命名空间为 chrome-infra-packages 上的 flutter
View Infra SLO metrics 包含基础设施、发布与 roll 指标的工程效能看板 Googlers 内部 DataSite(Googler 内部资源)

原文档末尾还说明:Googlers 可以通过内部的 go/flutter-self-service 入口访问这些服务的内部版本(内部链接不在此输出)。

三、.ci.yaml:一切 CI 自助服务的入口

.ci.yaml 是所有「Flutter contributors」可用基础设施服务的共同载体。从当前仓库可以确认它的实际规模与形态:

  • 仓库根目录的 .ci.yaml 约 7800 行,其中定义了 800 多个目标(name: 条目),文件头部注释说明其用途:「Describes the targets run in continuous environment」,即 Flutter 基础设施依据该文件为每次提交生成任务清单。
  • 仓库同时声明 enabled_branchesmaster 与形如 flutter-\d+\.\d+-candidate\.\d+ 的发布候选分支),并通过 platform_properties 按平台(linuxlinux_android_emustaging_build_linux 等)声明共享的 dependenciesoscoresdevice_type 等属性,目标可以继承这些平台级配置。

.ci.yaml 中的真实目标可以看到目标定义的典型结构。例如 FirebaseLab 目标(约 L759 起):

- name: Linux firebase_release_smoke_test
  recipe: firebaselab/firebaselab
  timeout: 60
  properties:
    dependencies: >-
      [
        {"dependency": "android_sdk", "version": "version:37v2"},
        {"dependency": "open_jdk", "version": "version:21"},
        ...
      ]
    tags: >
      ["firebaselab"]
    task_name: release_smoke_test
    physical_devices: >-
      [
        "--device", "model=shiba,version=34",
        "--device", "model=redfin,version=30",
        "--device", "model=griffin,version=24"
      ]
    virtual_devices: >-
      [
        "--device", "model=Nexus5.gce_x86,version=21",
        ...
      ]

其中 recipe 指向执行该任务的 recipe(如 firebaselab/firebaselabdevicelab/devicelab_droneflutter/flutter_drone),runIf 字段(如 dev/bots/**.ci.yamlengine/**)用于按改动路径过滤触发,bringup: true 用于新目标上线期间的配置传播(本仓库中多处目标使用了该字段,见 .ci.yamlLinux snippets 目标)。

引擎侧的 .ci.yaml

引擎构建使用独立的 .ci.yaml,位于 engine/src/flutter 目录。根据 构建配置 README,该文件把各组件串起来:用 properties 中的 config_name 指向 ci/builders 下的构建定义文件,由 engine_v2 recipe 读取、分片、收集产物并上传。README 中给出的示例:

- name: Mac mac_android_aot_engine
  recipe: engine_v2/engine_v2
  timeout: 60
  properties:
    config_name: mac_android_aot_engine
    $flutter/osx_sdk: >-
      { "sdk_version": "16c5032a" }

四、引擎构建配置:Build Definition Language 详解

engine/src/flutter/ci/builders/ 目录包含全部引擎构建配置(linux_android_aot_engine.jsonmac_ios_engine.jsonlocal_engine.json 等)。该目录的 README 完整描述了这套「Flutter Engine Build Definition Language」:一个构建由若干 sub-buildsarchivesgeneratorsdependencies 组合而成,底层由 Engine Recipes V2 与 GN+Ninja 执行,产物可通过 CAS(内容寻址存储)复用。

配置文件骨架

一个构建配置是包含 builds、tests、generators、archives 的 JSON 文件(空项可省略):

{
   "builds": [],
   "tests": [],
   "generators": {
       "tasks": []
   },
   "archives": [
   ]
}

配置文件必须提交到 ci/builders 目录,engine_v2 recipe 从这里读取;每个配置文件定义一个顶层 builder,会作为构建看板中的一列展示。

Build 组件

一个 build 是一个字典,包含 gn 命令、ninja 命令、0 个或多个 generator 命令、本地测试与输出产物,且各部分均可选(允许「只有 gn+ninja」「只有 generator」的组合)。高层结构:

{
   "archives": [],
   "drone_dimensions": [],
   "gclient_variables": {},
   "gn": [],
   "name": "host_debug",
   "generators": [],
   "ninja": {},
   "tests": [],
   "postsubmit_overrides": {}
}

各子项含义(均来自 README):

  • gn:传给引擎 tools/gn 脚本的字符串列表,形式为 --flag=value--flag value。例如 "gn": ["--runtime-mode", "debug", "--prebuilt-dart-sdk", "--build-embedder-examples"] 会用预构建 Dart SDK 准备 host debug 配置并构建 embedder 示例。
  • ninja:包含 config(gn 生成的配置名)与 targets(Ninja 目标列表)两个键,例如构建 flutter/build/archives:artifactsflutter/build/archives:embedder
  • drone_dimensions:形如 "os=Linux""device_type=none" 的键值对列表,用于选择运行 sub-build 的 bot;同一维度可用 | 分隔多个允许值。
  • gclient_variablesgclient sync 时传入的变量,常用来增删 gclient 依赖,如 "download_android_deps": false 可避免下载不需要的 Android SDK 依赖。
  • archives:告诉 recipe 构建生成了哪些产物、上传到哪里。默认整个 build 输出归档到 CAS 供全局测试依赖;type 支持 gcs(供 flutter tool 消费的产物应使用 GCS)与 cas(开发期检查用);realmproductionexperimental(后者在路径前加 experimental 前缀,避免与生产产物互相干扰);不需要 CAS 归档时加 "cas_archive": false
  • tests(本地测试):每个测试项含 language(执行脚本的解释器,如 python3、bash)、test_timeout_secs(覆盖默认 1 小时超时)、nameparameters(可含魔法变量)、script(相对 checkout 目录的路径)、contexts(如 android_virtual_devicemetric_center_token)、test_if(分支正则,默认处处运行)。本地测试不应引用 commit checkout 与 gn/ninja 输出之外的任何东西。测试脚本运行在 deferred 上下文中(日志上传完成后才标记失败),tester/builder recipe 提供 FLUTTER_LOGS_DIR 环境变量指向临时目录,测试结束后其内容会被上传到 GCS。
  • postsubmit_overrides:用于覆盖顶层构建属性(目前仅支持 gn),例如 presubmit 用 --runtime-mode debug、postsubmit 用 --runtime-mode release
  • Magic variables${FLUTTER_LOGS_DIR}${LUCI_WORKDIR}${LUCI_CLEANUP}${REVISION}(postsubmit 为引擎 commit,presubmit 为空串)。限制:魔法变量目前只能单独出现在参数串中(["${FLUTTER_LOGS_DIR}"] 合法,["path=${FLUTTER_LOGS_DIR}"] 不合法)。

Generators 与 Global Tests

  • Generators 是组合两个或多个 sub-build 输出以生成产物的脚本(最典型场景是生成 Mac/iOS 通用二进制)。规范要求:跨 sub-build 的资源路径相对 checkout(src/)目录,输出路径相对 src/out;脚本负责生成最终产物(如打 zip);若产物是 Mac/iOS 的,嵌入签名元数据也是脚本的责任。task 字段含 nameparametersscriptlanguage(留空默认 bash)。iOS framework 生成脚本即 create_ios_framework.py,macOS 对应 create_macos_framework.py
  • Global tests 在独立 bot 上运行,可访问同一 orchestrator build 中所有构建的输出,分两种场景:用 tester recipe 跑 flutter/flutter 分片测试(shard 名对应 dev/bots/test.dart 中定义的分片,test_dependencies 声明测试所需依赖);用 tester_engine 跑需要多个 sub-build 输出的复杂引擎测试(dependencies 按 build 名引用,经 CAS 挂载到 checkout/src/out;task 支持 max_attemptstest_timeout_secs 等)。
  • Global archivessource(相对 checkout 仓库)、destination(相对 <bucket>/flutter/<commit>)、realm(production/experimental)三键描述上传,例如把 out/debug/artifacts.zip 上传到 ios/artifacts.zip

README 还给出了「全局 generator 本地排障」流程:安装 CAS 工具、按 gclient 检查出引擎、用 LUCI 构建页中记录的信息执行 cas download -cas-instance projects/chromium-swarm/instances/default_instance -digest <digest> -dir ./ 下载各 sub-build 产物,然后照抄构建页中的命令本地运行 generator(如 python3 flutter/sky/tools/create_ios_framework.py --dst out/release --arm64-out-dir out/ios_release ...)来复现与验证产物问题。

一个真实配置文件的观察

mac_ios_engine.json 为例,可以印证 README 描述的字段与仓库内实践一致:文件头部 _comment 声明该文件只放产出发布产物的 builds、测试放到其他 mac_ 构建定义文件;luci_flags 中启用 upload_content_hash;每个 build 带有 drone_dimensions(如 "os=Mac-15.7""cpu=arm64""device_type=none")、gclient_variablesdownload_android_depsuse_rbe 等)、gn 参数列表(--target-dir--ios--runtime-mode 等)。

五、FirebaseLab 测试:用 .ci.yaml 接入真机/模拟器云测

Flutter-FirebaseLab-Tests.md 说明:FirebaseLab 测试用于构建 Flutter 应用并在不同版本的模拟器与真机上运行,由两部分组成——firebaselab recipe 与 .ci.yaml 配置。recipe 支持三个属性:

  • physical_devices:指定连接到 Firebase 基础设施的真实硬件;
  • virtual_devices:指定要使用的虚拟设备(AVD);
  • task_name:选择要构建的集成测试,即 dev/integration_tests 下的子目录名(如 android_views、channels、release_smoke_test)。

设备 ID 使用 Firebase 定义的 MODEL_ID 格式,可用 gcloud firebase test android models list / gcloud firebase test ios models list 查询可用机型。属性格式示例:

physical_devices: >-
    [
       "--device", "model=oriole,version=33",
       "--device", "model=griffin,version=24"
    ],
virtual_devices: >-
    [
      "--device", "model=Nexus5,version=21",
      "--device", "model=Nexus6P,version=27"
    ]

recipe 的执行工作流:

  1. 读取 physical_devices,非空则为 task_name 指定的集成测试构建 app bundle;
  2. 读取 virtual_devices,非空则构建 APK(对虚拟设备使用 APK 可防止其选错二进制而触发运行时转译);
  3. gcloud firebase 命令上传二进制,并把测试执行委托给 Firebase Lab;
  4. gcloud 命令阻塞直至执行完成;
  5. recipe 读取 logcat,只要 logcat 中没有 E/flutter 即判定成功;
  6. 失败时最多重试 3 次;
  7. 支持 infra_failure_codes(1、15、20),防止 FirebaseLab 基础设施故障导致关闭树(close the tree)。

添加一个 FirebaseLab 测试的步骤:

  1. dev/integration_tests 选择要用的集成测试;
  2. gcloud firebase test ... models list 选定物理/虚拟设备;
  3. flutter/flutter 的 .ci.yaml 中编写目标配置,提供 task_namevirtual_devicesphysical_devices 与 recipe 属性;
  4. 发起 PR,presubmit 会对 YAML 格式做基础校验;
  5. 等待配置传播;
  6. 修复问题后移除 bringup: true,让 presubmit 端到端验证。

按约定目标名格式为「<host os> firebase_<model id>_<taskname>」,文档给出的完整示例为 Linux firebase_oriol33_abstract_method_smoke_testrecipe 固定为 firebaselab/firebaselabtimeout 单位为分钟,多数场景 1 小时足够(除非低配设备排队超过 30 分钟);dependencies 顶层依赖在 firebaselab 测试间共享(可复制现有目标的值,升级 Android SDK 时才需要改);tags 设为 ["firebaselab"],用于指标采集与 swarming 过滤。

六、Devicelab Android 模拟器测试

Testing-Android-Changes-in-the-Devicelab-on-an-Emulator.md 描述了另一条自助路径:Devicelab 通常用于真机测试,现在也支持开发者通过 LUCI recipe 在 Android 模拟器上测试 Android 改动,同样只需在仓库的 .ci.yaml 中指定新测试。新增一个全新目标的参考定义:

- name: Linux_android android_defines_test
  recipe: devicelab/devicelab_drone
  presubmit: true
  timeout: 60
  dimensions: {
    kvm: "1",
    cores: "8",
    Machine_name: "n1-standard-8"
  }
  properties:
    device_type: "none"
    task_name: android_defines_test
    use_emulator: "true"
    dependencies: >-
      [
        {"dependency": "android_virtual_device", "version": "31"}
      ]
    tags: >
      ["devicelab", "linux"]
    timeout: 300

各字段要点:

  1. name 的平台前缀可选 LinuxLinux_Android,优先选 Linux_Android(能获得更多所需依赖);
  2. recipe 固定为 devicelab/devicelab_drone,由它负责启动模拟器并驱动测试;
  3. presubmit: true 表示每个 PR 都运行该测试,这是尽早发现 bug 的最佳做法;
  4. 顶层 timeout 为整数(分钟);
  5. dimensions 用于声明需要支持嵌套虚拟化的机器,按示例设置;
  6. properties 中:device_type 必须为 none(确保使用未挂 Android 手机的机器,否则会有问题);task_name 是任务名;use_emulator 告诉 recipe 需要创建模拟器;dependencies 可覆盖 android_virtual_device 的 API 版本;tags 设为 devicelablinux;内层 timeout 是测试运行被强杀前的时间上限。

更新已有目标时只需五步:添加上述 dimensions;设 device_type: "none";在 properties 中加 use_emulator: "true"(注意是字符串而非布尔值);为模拟器版本添加 dependency;移除任何表示 android 设备的 tags(那是基准测试用的,留着会导致部分设备检查失败)。

七、代码签名元数据(Codesigning)

Code-signing-metadata.md 解释了引擎二进制如何接入签名基础设施:Flutter 引擎二进制由 GN + Ninja 构建(引用 ci/builders 下的 JSON 配置),发布时需要为 mac 引擎二进制做代码签名,以证明来源可信、未被篡改且不会被 Gatekeeper 隔离。每个引擎二进制要么带 entitlements 签名、要么不带(entitlements 结合开发者账号授予特定权限),例如 impellerc 带 flutter entitlements 签名,而 .dylib 通常不带。

该文档区分了两种产生引擎二进制的方式,并分别给出加/改元数据的步骤:

  • 通过 BUILD.gn 构建规则:先沿产物 GN 目标的 deps 字段递归追踪到叶子节点(能产出该二进制、且不再依赖其他能产出该二进制的目标的最小 GN 目标);然后把二进制名加入叶子节点 metadataentitlement_file_path(带 entitlements)或 without_entitlement_file_path(不带)字段。若目标此前从未签过名,还需要在产出 zip 产物的同文件中加一个收集 data keys 的构建规则(generated_file + data_keys = ["entitlement_file_path"]),并把收集出的 entitlements.txt 嵌入 zip 产物(host_os == "mac" 时把 :artifacts_entitlement_config 加入 deps、把生成的 entitlements.txt 映射为 zip 内的 entitlements.txt)。文档以 impellerc 为例:在 artifacts.zip 的 deps 中找到 //flutter/impeller/compiler:impellerc,在 impellerc 的 BUILD.gn 中定位 impeller_component("impellerc"),并把 impellerc 加入其 metadata.entitlement_file_path
  • 通过全局生成器脚本(通常 .py 文件):判别依据是该产物列在 builder JSON(如 mac_ios_engine.jsonmac_host_engine.json)的 archives -> destination 中(如 darwin-x64/FlutterEmbedder.framework.zip),而不是 builds 字段中(如 darwin-x64/artifacts.zip)。操作是找到 generators -> tasks -> script 指向的脚本(iOS 为 sky/tools/create_ios_framework.py,macOS 为 sky/tools/create_macos_framework.py),把二进制名加入其中以 with_entitlements / without_entitlements 为后缀的变量(文档示例变量名为 ios_file_with_entitlementsios_file_without_entitlementsfilepath_with_entitlementsfilepath_without_entitlements)。注意:当前仓库的这两个脚本中未直接检索到上述变量名,说明脚本实现可能已演进,实际操作时应以脚本当前内容为准。
  • 其他产物:签名能力本身实现为 flutter recipes 中的一个 recipe module,因此也可用于签名任意经 recipe 构建的 Flutter 产物(例如 iOS USB 依赖),用法是产物构建完成后把文件路径传给签名 recipe module 并调用其函数。

八、发布(Release)类自助服务

原文档 Release 一节的四项服务主要面向 Googlers/Release Engineering,涉及内部流程,此处完整继承其定义:

服务 说明 人群
Create non flutter release candidate branches 为 flutter 以外的产品创建发布候选分支的自助服务 Googlers
Request 1P cherry picks 向发布候选分支申请 cherry pick 审批 Googlers
G3 Fixes 在 roll 过程中自动应用 G3 修复 Googlers
Single command releases 以多方审批方式创建第三方 flutter 发布 Release Engineering

这些服务的具体文档位于 Googler 内部(g3doc 等),仓库内不附对应文件;其操作入口与审批流程请以内部文档为准。

九、安全(Security)类自助服务

服务 说明 人群
Vulnerability scanning and fixes validation 对 C/C++ 第三方依赖的自动漏洞扫描与修复验证 Flutter organization members
Request write access to non-prod GCP projects 申请非生产 GCP 项目的写权限 Googlers
Rolling non-auto-updating 3p mirrored deps 依赖了不会自动同步上游变更的镜像时,需要手动 roll Googlers

其中「Vulnerability scanning and fixes validation」位于 Flutter GitHub 仓库的 security 页面(code scanning),是组织成员可用的自助入口;后两项的文档位于 Googler 内部。

十、实操小结:如何正确选择与编写自助服务配置

结合以上各节,可以归纳出在本仓库使用自助服务时的决策路径:

  1. 要在 CI 中新增验证任务(框架测试、集成测试):改仓库根目录 .ci.yaml,选对 recipeflutter/flutter_dronedev/bots/test.dart 的分片模型、devicelab/devicelab_drone 走设备/模拟器、firebaselab/firebaselab 走 Firebase 云真机),用 runIf 控制触发路径、bringup: true 帮助新配置传播、tags 便于过滤与统计。
  2. 要新增/修改引擎构建:在 engine/src/flutter/ci/builders 下按 Build Definition Language 编写/修改 JSON(builds/tests/generators/archives),并确保 engine/src/flutter/.ci.yaml 中通过 config_name 引用它。
  3. 要上云真机跑集成测试:参照第五节的属性与工作流,复用既有 firebaselab 目标的 dependencies,机型用 gcloud 查询确认。
  4. 要在模拟器上跑 Devicelab 任务:按第六节补齐 dimensionsdevice_type: "none"use_emulator: "true"(字符串)与 android_virtual_device 依赖,并清理 android 设备 tags。
  5. 要新增/改名引擎二进制并确保正确签名:按第七节区分 GN 构建与全局生成器两条路径修改元数据。
  6. 权限与人群校验:先对照第一节确认自己属于哪类人群,再访问对应服务;Googler 专属服务(重跑 presubmit/postsubmit、LED 并行测试、SLO 看板、GCP 权限、发布候选分支与 1P cherry pick 等)需走内部入口。

本文所有结论均来自仓库内 docs/Flutter-Self-Service-Index.md 及其引用的 docs/infra/Flutter-FirebaseLab-Tests.mddocs/platforms/android/Testing-Android-Changes-in-the-Devicelab-on-an-Emulator.mddocs/engine/release/Code-signing-metadata.mdengine/src/flutter/ci/builders/README.md.ci.yaml 等实际文件;对仓库外资源(cocoon 仓库、gcloud/内部看板)仅说明位置,未将其内容当作仓库事实。

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