本文介绍如何使用 GitHub Actions(GHA)为 Exercism 语言轨道搭建持续集成(CI)工作流。它提供最佳实践和示例,供你参考,帮助你打造自己快速、可靠、健壮的 CI 工作流。这个文件夹里的 GHA 工作流可以改造成适配任何 CI,因为基本结构保持不变。
本文将:
这些工作流文件的示例实现可以在exercism/javascript中找到。
文档接下来的部分旨在解释这些工作流如何运作。如果你很赶时间,只想从 Travis 或 Circle 切换到 GHA,而不优化 PR 脚本,可以看看我们关于从 Travis 迁移的约 10 分钟指南。
用来检查代码仓库内容完整性的推荐操作如下:
configlet lint,以检查config.json
v3要求新增文件;以后这可能会移到configlet)还可能有针对特定轨道的操作。例如:
也许你还想要更多便利性检查,例如:
对每个操作,想清楚它应该多久运行一次。
configlet lint 非常重要(因为config.json一旦出问题,轨道就可能崩溃),所以它大概应该一直运行,不过每次提交只需运行一次。把应该运行的操作同时做成可以在本地运行,会非常有帮助。这意味着真正干活的脚本也能手动运行。为此,不要把操作内联到工作流文件里,而要创建一个独立脚本。例如,检查 stub 完全可以直接在工作流文件里用 bash 搞定,但这里建议改为新建一个可执行脚本scripts/ci-check。
“可是这个命令很短啊,比如
eslint . --ext ts --ext tsx。”当这个命令需要更新时,你现在得在文档、工作流文件以及_维护者的脑子里_的所有地方都更新一遍。把它抽成一个脚本就能解决这一切。而且读工作流文件本身就非常让人头大。
scripts/pr和scripts/pr-check脚本(见模板)运行时接收多个参数,每个参数对应这个 PR 里改动或新增的一个文件。例如,如果two-fer有更新,调用可能长这样:
scripts/pr exercises/two-fer/README.md exercises/two-fer/.meta/example.ext
建议针对发生变动的_练习_运行操作,而不是针对发生变动的_文件_。因为改动一个文件很可能会牵动整个练习(想想配置、软件包)。
还没准备好? / 太复杂?
在实现这项优化之前,完全可以放心地先忽略它!迁移指南暗示可以稍后再加。如果忽略传入的参数,所有检查都会在所有练习上运行。这完全没问题。只是会花更长时间。
如果轨道有一个单独的“顶层”依赖文件和/或其他配置文件,就加一个完整性步骤(它和scripts/sync或bin/sync一起存在,后者会把所有配置文件复制到所有练习),用来确保顶层/基础文件与复制到各练习目录里的文件一致。这样一来,依赖可以更新、在整个代码仓库里同步,我们也能确保所有练习的配置一致。
实现这一点的常见方法是使用校验和。Ubuntu(以及各种其他 Linux 发行版)自带一个叫sha1sum的工具,但用_任何_方法把配置文件哈希或归约成一个校验和值(md5、sha1、crc32)都行:
$ sha1sum README.md
cd58091c5043bf21f00d39ff1740d8b2976deeff *README.md
如果轨道使用了需要访问 GitHub 令牌或其他密钥的其他工作流,最佳实践是把工作流里用到的所有操作固定到某个特定提交。详细信息见 GitHub 的安全加固指南。
例如:
- uses: julia-actions/setup-julia@v1
+ uses: julia-actions/setup-julia@d26d1111976eae5f00db04f0515ab744ec9cd79e # 1.3.1
如果工具链用锁定文件来管理依赖,可以考虑把它提交进代码仓库,并在工作流文件里使用“冻结的锁定文件”。例如:npm ci、yarn install --frozen-lockfile和bundle install --frozen。这能确保更改依赖时锁定文件是最新的,并防止恶意软件包混进来。
在这个目录里,至少有下面这些模板:
configlet.yml:这个工作流会拉取最新的 configlet 二进制文件并对代码仓库运行 lint。每次提交都会运行。对于 PR,会在实际提交和“合并后”的代码树上运行。ci.yml:这个工作流只在 main 分支上运行,每次提交运行一次。
pr.ci.yml:这个工作流只在 PR 上运行,每次提交运行一次。
非 PR 工作流也可以通过workflow_dispatch触发。
每个文件顶部都列出了需要提供哪些“脚本”。如果你想把这些做成二进制文件,就把scripts/xxx换成bin/xxx。有些工具链会_要求_二进制文件放在bin文件夹里。
scripts/ci:一个脚本,应该用示例解答对所有练习进行构建和测试,并与测试比对scripts/ci-check:一个脚本,应该对所有练习运行 lint,并可选地检查 stub、配置完整性等scripts/pr:和scripts/ci相同,但只运行根据输入路径解析出的练习scripts/pr-check:和scripts/ci-check相同,但只对根据输入路径解析出的文件或练习运行如果你遇到任何问题,或者想让人帮你审阅工作流,请 @ 一下@exercism/github-actions团队。
改动了某个本应触发所有练习 CI 运行的顶层文件
在撰写本文时,
pr.ci.yml只支持基于“扩展名”的测试。理想情况下,它应该更新为在某些文件发生变化时始终触发(比如用于运行测试的二进制文件)。不过,这类改动往往很少发生,而且由维护者来做,所以ci.yml在 main 上始终对所有东西运行,大概已经足够安全了。
在 Windows 上创建了 scripts/xxx 文件,结果它在 {other OS} 上不工作
默认情况下,在 Windows 上创建的文件,其 git 索引里不会嵌入关于可执行权限的元数据,因为 Windows 的权限模型不一样。Git 默认会使用 git 索引的元数据来判断文件在基于 POSIX 的系统上是否应该可执行,因此会让
scripts/xxx文件变成不可执行。
git update-index --chmod=+x scripts/xxx
git commit -m "Make scripts/xxx executable"