Laminas CI自动化的详细指南

90 阅读10分钟

Laminas项目在主项目、Laminas API工具和Mezzio之间有近200个存储库,需要维护的东西很多,保持对进入的补丁的关注是一项艰巨的任务,更不用说创建发布。

这就是为什么在过去的一年里,我们花了很多时间来简化我们的流程;我们希望能够快速而自信地审查、合并和发布变更。 为此,我们开发了一些GitHub操作,以使这些流程对我们的维护者来说尽可能地简单。

自动发布

第一个动作是Marco Pivetta(又名Ocramius)的创意,他希望有一种方法能使发布尽可能简单。

在这之前,我们有一个相当复杂的过程:

  • 如果一个拉动请求是针对我们的 "主 "分支。
    • 合并到 "master "中
    • 合并到 "develop"(这通常会导致合并冲突,因为各分支之间的CHANGELOG.md 文件不同)。
    • 在 "master "上创建一个分支,设定发布版本
    • CHANGELOG.md中调高发布版本
    • 将发布分支合并到 "master "中
    • 将发布分支合并到 "develop "中(同样,合并冲突)。
    • 给发布版打上标签,将相关的CHANGELOG.md 条目复制到标签描述中去
    • 推送该版本
    • 在GitHub上为该标签创建一个发布版本,并再次将CHANGELOG.md 中的条目复制到描述中去
  • 如果一个拉动请求是针对我们的 "开发 "分支的:
    • 合并到 "开发 "中
    • 将 "develop "合并到 "master "中
    • 在 "master "上创建一个分支,设置发布版本
    • CHANGELOG.md中提升发布版本
    • 将发布分支合并到 "master "中
    • 将发布分支合并到 "develop "中
    • 将 "develop "分支的CHANGELOG.md 文件中的发布版本提升到下一个次要版本
    • 给 "master "中的发布版本打上标签,把相关的CHANGELOG.md 条目复制到标签描述中。
    • 推送该版本
    • 根据标签在GitHub上创建一个版本,并再次将CHANGELOG.md 中的条目复制到描述中。

围绕标签和创建GitHub版本的很多工作都由我的keep-a-changelog工具处理,但这仍然是工作,而且有很多模板和繁忙的工作。

Marco的大想法是:如果我们把问题和拉动请求分配给GitHub里程碑,并且在里程碑关闭时自动创建版本,那会怎么样?

这导致了我们的自动发布GitHub动作的诞生。

要使用它,你需要在你的仓库中创建发布分支,以语义版本命名,形式为{MAJOR}.{MINOR}.x (末尾有一个字面的".x")。(这有一个很好的副作用,就是可以从我们的分支中删除 "主 "字。)然后你创建里程碑,为你想要创建的下一个版本命名。1.2.3,1.3.0,2.0.0 。从这里开始,你可以为你的应用程序添加一个小的工作流程,以及一些秘密(一个GPG签名密钥,一个Git作者和用于标记发布的电子邮件,以及可能的GitHub特权令牌,以允许创建一个合并请求;稍后将详细介绍)。

当你分流时,把你的问题和拉动请求分配给里程碑。 当所有与里程碑相关的问题和拉动请求都完成时,你就关闭里程碑,然后工作流就开始工作。

工作流做什么:

  • 它拉出里程碑的描述。
  • 它提取问题和拉动请求的列表,以及创建这些问题和请求的人,以创建一个发布说明的列表,详细说明已关闭的问题/拉动请求。
  • 如果你有一个Keep A Changelog格式CHANGELOG.md ,它将更新发布的条目,添加里程碑描述和前面步骤中拉出的发布说明,以及设置发布日期,将变化推回分支。
  • 它使用签名密钥和git作者/电子邮件创建一个标签,将描述设置为变更日志条目或前两步的信息,完成后推送该标签。
  • 它在GitHub上创建一个发行版,使用标签描述中提供的相同说明。
  • 如果有更新的发布分支存在(例如,如果你发布的是 1.2.3,而 1.3.x 分支存在),它会向该分支创建一个 "合并 "请求,并说明这两个分支之间的差异。
  • 如果没有较新的发布分支存在,它会创建一个新的次要发布分支(例如,如果你正在发布 1.2.3,它会创建 1.3.x 分支)。
  • 它将默认分支切换到下一个发布分支。
  • 如果你有一个CHANGELOG.md 文件,并且刚刚创建了一个新的发布分支,它会为下一个次要版本添加一个条目。
  • 它为下一个补丁、小版本和大版本创建里程碑,如果它们还不存在的话。

从本质上讲,这个动作将繁忙的工作自动化了,这样维护者就可以轻松地发布,并且经常发布

一路走来,我们发现:合并请求仍然会引起问题,因为CHANGELOG.md 文件中会有差异。所以我们想出了一个解决方案,因为工作流程中已经支持了:我们现在把过去保存在CHANGELOG.md 文件的信息保留在里程碑描述中,使用相同的格式。由于里程碑描述被动作拉出,并用于标签和发布说明,它具有相同的效果(把信息放在语义上属于它的地方),同时消除合并冲突(因为该文件可以被消除)。

这个新的工作流程非常成功。 我们发现,我们把大部分时间花在了分流上(确定我们是否会处理它,如果是的话,修复或功能应该针对哪个分支),而把更多的时间花在了提供反馈上。 一旦我们能够接受一个补丁,合并和发布之间的时间只需要关闭里程碑和运行动作的时间(我们已经缩减到不到一分钟了!)。

在任何地方使用!

虽然这个动作使用PHP来完成它的工作,但它可以在任何仓库中使用!我们甚至用它来为我们的其他GitHub动作创建发布,这些动作主要是用JavaScript和Bash编写。

持续集成

为了合并一个拉动请求,我们需要确保它通过我们的QA检查,通常包括单元测试、编码标准验证和静态分析。 我们也希望增加我们的QA检查,包括诸如文档的提示和文档链接检查。

我们传统上使用Travis-CI,但从去年年底开始,Travis对OSS的使用做了一些政策上的调整,我们发现我们的时间在每月中旬就会耗尽。 因此,我们需要一个新的解决方案,我们决定是时候弄清楚我们真正想要的CI是什么。

我们想到了什么:

  • 我们非常厌恶每次有新的PHP版本需要支持时都要更新矩阵。
  • 我们也不喜欢在添加新的 QA 工具时更新矩阵;这是个很容易忘记的步骤。
  • 此外,在一个给定的矩阵项目上运行多种类型的检查意味着我们最终要玩打地鼠游戏:我们修复了一个QA项目,却发现下一个检查失败了;我们修复了一个,却发现下一个也失败了;等等。
  • 我们真的希望尽可能多的工作能够并行运行,而不是在 repo 和组织层面上受到限制(Travis 限制我们在任何时候在整个组织中只能有 5 个连续的工作,这有时会导致一个很长的队列)。
  • 我们希望有最低限度的配置,如果可以实现的话,零配置是我们的目标。

为了解决这个问题,我与Marco和Cees-Jan Kiewiet(又名Wyrihaximus)合作,以确定存在哪些可能性,以及哪些方法可能有效。Luís Cobucci也提供了一些关于创建和发布Docker镜像以支持GitHub Actions的有用信息。

结果是两个GitHub行动:

然后这两个行动都被引用到一个工作流程,有两个步骤

第一个动作,laminas-ci-matrix-action,分析你的提交以发现可能需要运行的已知QA工具:

  • PHPUnit(通过phpunit.xmlphpunit.xml.dist 的一个或两个的存在)。
  • phpcs (通过phpcs.xmlphpcs.xml.dist 的一个或两个的存在)
  • Psalm (通过psalm.xmlpsalm.xml.dist 中的一个或两个的存在)
  • yamllint 和 markdownlint (通过存在一个mkdocs.yml 和/或docs?/book/**/*.md 文件)

它也会检查你的composer.json 来决定要运行的 PHP 版本,以及要安装的依赖集(它总是包括最低和最新的支持,如果有composer.lock ,则是锁定的)。 从那里,它建立一个矩阵:

  • 每个PHP版本的依赖集有一个PHPUnit作业。
  • 在当前的 "稳定的 PHP"(目前是 7.4)上,对发现的每个其他 QA 检查有一个工作。

你有能力提供要安装的扩展,使用的php.ini 值,要排除的QA检查,以及通过配置运行的额外检查。

当动作检测到它是由拉动请求触发的,它也会执行与目标分支的差异,并且只运行基于更改的文件的检查;例如,如果只有文档文件被更改,它将只做markdown linting。

然后,该动作会创建一个JSON格式的 "输出 "字符串,包含所有要运行的检查,以后的步骤或工作可以使用。比如laminas-continuous-integation-action!

laminas-continuous-integation-action负责实际工作。它接受一个JSON格式的 "作业",其中包括:

  • 要使用的PHP版本。
  • 任何需要安装的额外扩展。
  • 任何要使用的php.ini 设置。
  • 要使用的依赖集(最低的、锁定的或最新的)
  • 一个要运行的命令(实际的QA检查)

当它运行时,它检查出指定的资源库参考,设置默认的PHP版本,安装任何需要的扩展,添加php.ini 配置,然后根据依赖集运行Composer来安装依赖。 从那里,它作为非特权用户运行指定的命令,从而执行QA检查。

这个动作也允许你在资源库中提供前/后运行脚本,这对诸如播种数据库或缓存的事情很方便。

在这两个动作和简化的工作流之间,我们已经能够创建一个CI解决方案,它将随着我们的需求而增长,而不需要定期更新。 事实上,我们已经扩展了几次它们的功能,而不需要改变已经在使用它们的资源库。 自从添加工作流以来,我实际上已经向几个资源库添加了新的QA工具,并惊喜地看到动作接收它并开始执行作业。

此外,GHA的基础架构使工作流可以并行运行,大多数工作要么全部并行运行,要么大量并行运行。 最终结果是,CI运行的工作在Travis上往往需要3-5分钟,现在在GHA上不到一分钟就完成了。 这种快速反馈使补丁的迭代更容易,使我们的维护者能够专注于补丁的完成情况,而不是它是否符合QA指南。

经验之谈

作为一个项目负责人,很多人认为我的工作是推送新功能。 然而,像我们这样多的软件库,以及其中许多需要的专业知识,我的工作是促进贡献。 我发现我们一直在开发的工具是一个巨大的福音,帮助维护者,包括我自己,指导新的贡献,让他们快速发布到世界。 由于这在过去经常是对Zend Framework和目前Laminas的批评,我对我们能够扭转这种情况,并为各地的PHP用户提供高质量的代码抱有很大的希望。