阅读 4784

给 Flutter 上 PR 究竟有多难

前言

先给大家做一个凡尔赛自我介绍。

各位好,鄙人是 Flutter Developer,匿名背景,Flutter 开源组织 (GitHub Flutter Team) 成员之一、flutter.cn 及 CFUG 主要协作之一。平常专业对 Flutter 萌新执行劝退,对 Flutter 老手执行垃圾代码打击,善于劝 Flutter 开发者们耗子尾汁。

自 19 年入坑 Flutter 到现在,为 Flutter 提交了 8 个 PR(也不是很多),包含 2 个 breaking change,2 个样式调整,2 个功能修复,1 个 SDK 作者名录添加~可以从下方截图中看到,有些 PR 很轻松的解决了,有些由于各种原因关闭了,有些 PR 包含了大量的讨论。除了轻松合并的 PR 以外,大多数的 PR 都指向了一个字:

今天就来给大家讲讲,到底有多难。

第一步:找到一个可以PR的点

相信我,这绝对是你在 Flutter PR 之路上最简单的一步。Flutter 由于所有 SDK 内容都开源,得以让每一个开发者都能随时随地的查看 Flutter 的任意一行源代码。

这么多内容,从中找出一个错误是非常简单的。举个🌰:

Flutter 中或多或少隐藏着一些文档错误,某天兴起翻翻文档翻翻源码,也许马上就会发现一些小错误,啪的一下,很快嗷。虽然值得大家去发现,但提醒大家不要用来刷 PR,因为没什么用。写代码要讲码德。

再举一个🌰:

某一天你在把玩 Flutter Gallery 的时候突然发现:“天哪,这个图标竟然表示了相反的意思!这一定是个错误!👴🐛了!”接着兴冲冲地准备提交一个 PR。

很好,接下来我们要做什么呢?

第二步:准备一份拷贝的项目

有曾经为其他项目做过 PR 的朋友应该比较熟悉流程,在这里简单介绍一下。

  • Fork 项目仓库
  • 创建修复分支
  • 提交改动的内容
  • 发起 PR
  • 等待审核和处理

如果是小改动,且你比较熟悉 GitHub 的使用,可以完全在网页端将改动全部完成,而不需要将整个 Flutter SDK 拉取至本地(因为仓库很大和懂得都懂的原因)。

如果是较大的改动,按照以下的步骤进行操作:

  • 将 fork 的仓库拉至本地
  • 配置远端 upstream 分支至 github.com/flutter/flu… 方便处理文件冲突
  • 本地起新分支准备开始改动

第三步:了解代码规范、行为守则

代码规范

与我们平常熟知的使用 dartfmtdart formatflutter format 进行格式化不同,想要在 Flutter 的仓库进行提交,你的任何一行代码都不能依赖格式化工具进行格式化。具体的代码风格指导在 Style guide for Flutter repo。在修改时 不考虑旧代码的格式化问题,除非你要对旧代码进行修正。

举个简单的🌰(非空安全),下面是一个经过工具格式化的 StatelessWidget

class AWidget extends StatelessWidget {
  const AWidget({
    Key key,
    @required this.b,
    this.c,
  })  : assert(b != null),
        super(key: key);

  ///...
}
复制代码

而 Flutter 要求在 SDK 里的代码是这样的:

class AWidget extends StatelessWidget {
  const AWidget({
    Key key,
    @required this.b,
    this.c,
  }) : assert(b != null),
       super(key: key);

  ///...
}
复制代码

瞪大眼睛,否则你可能马上要问了:What's the difference?

注意看 assertion 以及 super,Flutter 要求的代码,少了一个空格☝️。(亿点小细节)

一般较小以上的改动,在第一次提交后,都会碰到格式错误。这时可以在已经提交对应 PR 的 CI 列表中,找到名为 Linux analyze 的 task,查看不符合规范的详情。如果它没有出错,恭喜你,你的格式 80% 符合了 Flutter 的要求。

行为守则

Flutter 对所有贡献者提出了 行为守则 (Code of conduct)

在开始进行改动前,确保你认真阅读且理解守则。

第四步:确保新代码有被测试且能通过所有测试

在完成你的改动后,你可能会觉得大功告成了。但,这其实只是个开始。

所有的修改(除了一些文档错别字修改)都需要包含测试。在 Flutter SDK 仓库中也包含了历来的所有测试,每一个对应文件都有对应的 feature test, golden test, regression test。

如何编写测试,是相对比较困难的一步,好在 Flutter 其实有合适的工具对你的代码进行测试。这时你需要参考你提交更改的位置的对应测试来对你的新代码进行测试,例如:你更新了 lib/src/material/text_field.dart 的代码内容,那么你应该在 test/material/text_field_test.dart 中参考类似的测试进行编写。关于如何使用 Flutter 测试工具,请通过🔍以及 Flutter 单元测试 cookbook 学习如何对你的代码进行测试。

如果你的改动包含了一些行为变更,并且最终会导致一些旧的测试失败,那么这次改动就是一次 breaking change(破坏性改动)。破坏性改动需要一些额外的步骤进行处理。

在上传你的代码前,一定要在你的本地通过 flutter test 跑一遍 Flutter 所有的内置测试。如果发现了不能通过的测试,尽量在提交前就将其解决掉。

接下来,让我们继续下一步。

第五步:将你的改动提交至 Flutter

前往 github.com/flutter/flu… ,你会看到一条提示,提示你刚刚对你 fork 的仓库的某个分支做了提交,引导你进行 PR。点击提交,进入信息填写。

你会看到 PR 模板为你创建了许多内容,包含了 描述 (Description)相关 issues (Related Issues)测试 (Tests)检查表 (Checklist)破坏性改动 (Breaking Change) 五个方面。具体不进行赘述,按照内容填写即可(看不懂就翻译,翻译了还不懂就再见👋)。填写完成点击 Create pull request 即可开启 PR。开启后的 PR 内容类似下图(当时还不需要写描述,只需要链接到 issue 即可):

如果你的 PR 还未包含测试,你会看到下面的信息,提醒你需要增加测试。如果你认为真的真的不需要测试,并且需要申请测试豁免,你需要使用 Discord 前往 Flutter 的 #hackers-💭 频道与 @Hixie 进行申请。

这里需要说明一下,Flutter 合并 PR 会以 squash 的方式进行合并,意味着你中途所有的提交,最终都合并为一个,所以我们不用担心通常所说的 脏 PR 的问题。但是按照规范进行提交是参与开源项目维护的基本素质。

第六步:签署 CLA (Contributor License Agreement)

在向谷歌的仓库提交代码前,你需要确认并签署 Google CLA (Contributor License Agreement) 贡献者许可协议。

前往 cla.developers.google.com/ 进行签署。完成后回到 PR,@googlebot 并写上 @googlebot I signed it!

如果 googlebot 告诉你 CLAs look good,就说明你的 CLA 已经签署成功,下一步就是等待官方成员的 review 了。

第七步:与 reviewers 讨论你的 PR

这是 PR 环节里最难的一部分。不是所有的人都能在看到代码的第一时间就理解你的目的和结果,所以 reviewer 可能会向你提出一些问题,或是提出一些不一样的想法。

当然,reviewer 也有可能犯迷糊,所以不要害怕指正,一定要合理清晰地表达清楚观点。

根据 reviewer 的意见,你需要作出一些解答,更进一步的说明你的想法。同时还要认真考虑对方提出的建议是否可行,是不是更好。

第八步(破坏性改动):撰写设计文档

Design doc 是你提出的破坏性改动的设计文档。设计文档主要是为了说明你的改动的详细内容,一般会包括 总结 (Summary)动机 (Objective)背景 (Background)概览 (Overview)设计细节 (Detailed Design)开放性问题 (Open Questions) 以及 迁移计划 (Migration Plan)。如何撰写请参考官方的模板:https://flutter.dev/go/template

我写过的设计文档:https://docs.google.com/document/d/1S900lQaTPOsePnIeE7o3-Ia8f39VZaSv0JnjNyBfCvU

第九步(破坏性改动):撰写迁移指南

Migration guide 是针对你作出的改动,开发者需要如何进行迁移的指南。假设在你的改动中废弃了一个 API,那么在迁移指南中,你需要给出明确的从旧 API 迁移到新 API 的使用代码,供开发者进行迁移。所有的 Migration guide 都列举在了网站中:https://flutter.cn/docs/release/breaking-changes

我写过的迁移指南:https://github.com/flutter/website/pull/4883

第十步:做好 PR 被关闭/拖延的准备

某程序员坚持让我写这一节。确实,给 Flutter 提 PR 无法保证最终能否通过 member 的审核,有大约50%的几率你的 PR 无法通过。在 今年7月份我与 KC 进行交流 的时候就有说过,很多时候你无法得知 Flutter 官方究竟想要什么,导致你的初衷就很有可能不被接受,或者官方推荐你用另一种方式落地你的改动,最终使得你的 PR 走向关闭。

这两年糖果的成员们都给 Flutter 开了不少 PR,挑一个最精品的来说,是 法法 的瀑布流 widget:https://github.com/flutter/flutter/pull/59582

为什么会被关闭呢?因为官方说他们希望核心框架只提供构建 widgets 的基建,而所有的风格 widget 全部拆为 package 进行管理。听起来可能有一些扯淡,但细细品后还是不得不认同官方的看法。

还有非常经典的一个 PR:https://github.com/flutter/flutter/pull/32811

官方解释:设计如此。从此 as design 就作为一个 Flutter 梗在群里流传开了...

自从 Flutter 引入了 flutter-bot 帮助合并请求以后,拖延的情况已经比较少了。否则你不提醒,他们就会完全忘记 ,让我们来看看 龙总 的 PR:https://github.com/flutter/flutter/pull/21142

第十一步:等待合并 + 跟进后续工作

当以上的步骤都完成后,你的 PR 最终会加上一个 waiting for tree to go green 的标签:

当你的 PR 被加上了这个标签后,BOT 会在构建状态 (Flutter Build) 正常时自动合并你的 PR。通过这步后,就恭喜你成功地为千万人使用的框架提交了代码了~

不过千万别以为就这么结束了。如果运气好,你的 PR 不会让任何内部测试失败,那么就无事发生。但如果有任何一个内部测试失败了,你的 PR 就会面临被还原(虽然最后还是会再次落地)。

除此之外,如果你的 patch 一不小心产出了一些意想不到的行为,也有可能会引起广泛的 issues 以及官方提醒你进行跟进的通知。

结语

PR 需要慢工出细活,为大型项目提交代码也是如此,在开源社区如何与别人进行协作亦是一门艺术。普通的 PR 可能只需要 5-6 步就能完成,但在面对 Flutter 时则需要花更多的心思。在 Flutter 如火中天的时期,国内的社区环境也非常友好和活跃,也不断有更多的人回馈到 Flutter 本身。也希望有更多的开发者能够积极回馈。

比起 Flutter 的仓库的难度,flutter.cn / dart.cn 更为简单,也非常欢迎大家共同参与协作,同步并翻译官方的文档,为国内开发者带来更为便捷和实用的开发体验。

想知道糖果是什么?欢迎加入 糖果大家庭 ~ 在这里你会享受到 最纯正 的 Flutter 劝退体验☝️