跳至内容
GitHub 仓库 论坛 RSS 新闻源

Crystal 自动发布

Brian J. Cardiff

简介

正如我们在 2017 年底 分享的那样,我们今年重启了朝向 1.0 版本的计划。我们的第一步是改进发布的自动化。感谢 12 月和 1 月的部分 捐赠,我们设法将 80 个小时的工作投入到其中。感谢您的帮助!

过去的情况

很长一段时间以来,我们通过 omnibus-crystal 管理 Crystal 的发布和分发流程。尽管流程的某些部分是自动化的,但它涉及许多需要手动步骤,例如启动虚拟机以针对不同的发行版生成 pkg、deb、tar.gz 等。

当只有一两个人(Ary 和 Waj)负责发布语言版本时,此流程曾经足以满足我们的需求。但是随着项目和核心团队的壮大,我们担心流程的某些部分没有得到适当的记录,依赖于他们头脑中或机器中艺术品环境中的信息。

发布流程的另一个重要部分是在网站上发布文档。到目前为止,我们一直在使用 Travis 完成此操作。每当我们标记一个构建时,我们会让 Travis 将 docs 命令的输出同步到 S3。然后,通过一些路由重定向逻辑,我们会确保指向最新发布文档的链接(例如 /api/Array.html)会重定向到 /api/0.24.1/Array.html,这样我们就可以在文章或 Crystal 书籍中使用前者。

然后是 Docker。如今,不包含官方 Docker 镜像的发布似乎很奇怪。因此,在为每个平台构建和发布软件包后,会构建、标记并发布 Docker 镜像到 https://hub.docker.com/r/crystallang/crystal/。但这同样不是自动化的,我们有时甚至会忘记这样做几天。

此构建狂热中最后一块拼图是,CI 目前在 Travis 上运行,使用 Docker 镜像构建编译器,用于 Linux 和 Linux 32 位,而我们依赖于 CircleCI 进行 Mac 构建。顺便说一句,CI 中使用的 Docker 镜像在发布构建期间不会自动更新。

尽管所有这些都是手动流程,但 @waj、@asterite、@jhass、@matiasgarciaisaia 已经做出了巨大的努力来保持事情向前发展。

在几个版本之前,@RX14 编写了一个新的构建流程来改进某些 Linux 发行版的软件包。因此,我们决定放弃 omnibus,转而使用多阶段 Docker 镜像来提供带有 musl 和最新 LLVM 版本(尽可能)的编译器。

我们想要什么?

我们的主要目标是

  1. 完全自动化构建已发布二进制文件的流程,包括构建它们的机器的供应。
  2. 简化发布流程,以便更频繁地、无痛地进行发布。
  3. 持续测试,以在实际发布之前发现生态系统中的故障(包括最流行的 shard,如 DB、Kemal、Amber、Lucky 等)。
  4. 拥有统一的发布脚本(为了我们自己的理智)。

在尝试实现这些目标的同时,我们还想解决一些关于 CI 的技术债务

  1. 将所有平台的 CI 移动到单个 CI 环境。
  2. 避免手动发布后步骤,例如为 CI 发布 Docker 镜像。
  3. 尽可能更新和统一依赖项的版本。

我们做了什么以及发现了哪些“惊喜”?

您好,CircleCI 2.0

我们从一个在 CircleCI 1.0 上运行的 CI 设置迁移到一个 CircleCI 2.0 工作流程,该流程将对 Linux32、Linux64 和 OSX 构建进行压力测试。更改云 CI 配置始终是一项缓慢的任务:感觉就像您通过电子邮件发送了一个脚本,然后排队等待,因您遗漏的愚蠢细节而失败(很多次),然后一次又一次地重试。

Linux 64 位的夜间构建

在迁移运行 CI 的脚本后,我们开始着手为所有支持的平台生成最新的自动夜间构建的编译器镜像。

为了构建 Linux 64 位,我们现在使用由 @RX14 创建的新 distribution-scripts。迁移到这个几乎是完全直观的,除了需要进行一些更改,因为我们唯一使用这些脚本的时间是基于 0.23.1 发布 0.24.1 编译器(如果你想知道,0.24.0 实际上被撤回了)。

OSX 的夜间构建

为了构建 osx 编译器,我们将 omnibus-crystal 仓库迁移到了 distribution-scripts。最终,拥有一个仓库将简化事情。我们需要稍微调整一下过去的方式,以避免在 CircleCI 上花费太多时间 1

最终,我们可以完全摆脱 omnibus,但主要目标是自动化流程,而不是更改打包。

Linux 32 位的夜间构建

distribution-scripts 构建系统会生成 x86_64 Linux 软件包。要获得 32 位,有两种选择。

  1. 使用 omnibus 脚本,就像我们过去一样,但将其运行在 某种程度上官方的 Docker i386 镜像 中,而不是 32 位虚拟机,因为 CircleCI 不提供 32 位机器执行器。鉴于我们将 omnibus 脚本保留了更长时间,这似乎是一条简单的路径。
  2. 制作一个 distribution-scripts 版本,该版本将从以前的 32 位编译器构建 32 位编译器。
  3. 制作一个 distribution-scripts 版本,该版本将从以前的 64 位编译器构建 32 位编译器,并在中间进行交叉编译。

执行 2 或 3 会更改打包,并将更改与 64 位当前版本中引入的路径对齐。但这将比选项 1 需要更多的工作。

因此我们尝试了选项 1。结果是 - 我们遇到了 Boehm 的 GC 中的一个错误,该错误特定于 32 位 Docker 容器,并在比 Omnibus 设置使用的版本更新的版本中得到修复。

因此我们无法在 Docker 容器内使用 Crystal 0.24.1(带有有问题的 GC 版本)。因此,我们使用更新的 GC 版本手动构建了用于 32 位的 Crystal 0.24.2。这将使我们能够使用 distribution-scripts 在将来发布 0.25.0。可能仍然坚持使用选项 1。

底线:0.24.2 没有 32 位的夜间构建软件包,但我们应该能够在将来引入它们。

文档的夜间构建

将文档包含在流程中也很棒。它让我们可以停止自动从 Travis 发布(针对标签和分支)。这样一来,我们现在所有人都可以预览夜间构建/已标记的文档,跳转到未发布的旧版本,最重要的是,在发布软件包时同时将文档发布到主站点,而不仅仅是在标记仓库时。这在过去会导致一些混乱,因为访问站点会显示一些内容,但软件包却无法反映它们。

Docker 的夜间构建

我们想更进一步,简化用户如何检查夜间构建是否在他们的项目中运行。因此,我们做了一些工作来发布标记为夜间构建的 Docker 镜像。使用在构建过程中生成的最新 Linux 软件包,我们可以安装它并直接从 CircleCI 推送 Docker 镜像,即使该软件包尚未发布到 APT 仓库中。请注意,这些镜像的编译器具有发布优化,因此与官方发布的唯一区别将是版本描述(当然,只要夜间构建是在同一提交上执行的)。

我们将发布 crystallang/crystal:nightlycrystallang/crystal:nightly-build Docker 镜像。前者应该能够编译使用 stdlib 的应用程序,后者包括构建编译器所需的 LLVM 和依赖项。

在编译器仓库中保留血统

每次发布新的 Crystal 编译器时,下一个编译器通常是在新的编译器上构建的。分发和打包解决方案通常保持不变。为了不丢失编译器的血统,最好跟踪每个标签是如何构建的,在同一个编译器仓库中。为了实现这一点,我们让 distribution-scripts 接收参数来设置应该使用哪个现有的 Crystal 编译器来构建特定的新提交或标签。

Crystal 仓库现在包含对特定版本的 distribution-scripts 的引用,用于夜间构建。由于 CI 配置中现在已说明了有关先前编译器的信息,因此从现在开始,血统将在仓库本身中可用。一个额外的好处是,将来的不同分支将不会被限制为使用“最新”版本。

标记版本构建

构建标记提交的过程基本相同。这很好!这是构建过程更加顺畅的证明。

一旦 CircleCI 构建了发行版包和文档,接下来的步骤是

  1. 从 CircleCI 构建中下载工件。
  2. 签署软件包。
  3. 发布到仓库。
  4. 文档需要上传到 S3,并将新目录标记为最新。本质上与现在在 Travis 中所做的相同,但在发布软件包时作为一个整体触发。
  5. 使用签署的软件包构建 Docker 镜像并上传。

这些步骤是使用 crystal-lang/crystal-dist 手动执行的,因此签名密钥得以安全保存。最终,此仓库可能会与 distribution-scripts 合并,以便所有部分都保存在一个地方。

生态系统测试

如今,在 Docker、Vagrant、CI 和其他资源之间,可以自动化一些冒烟测试。我们在发布 0.24.1 时进行了一些这样的测试,并在分发之前发现了一些版本中的问题。但是,不幸的是,我们在标记之后才发现了这些问题。随着夜间包的可用性和标记过程的一些改进,我们预计在发布之前运行一些冒烟测试。这将有助于我们在早期检测到问题,也许可以向受影响的仓库提交 PR/问题。虽然编译器和标准库有大量的规范,但肯定在野外有一些有趣的场景。

今天,我们使用 这些生态系统测试 来检查(在 Darwin、Linux 和 Linux32 上)一些基本命令是否可以运行,堆栈跟踪是否有效,crystal-db 和驱动程序是否有效,以及网络框架 Amber、Lucky 和 Kemal 是否能够正常工作。

它并不完美,但至少它是一种努力,让我们在发布之前知道某些东西可能存在问题。

我们今后将如何处理?

让我们回顾一下,每个晚上和正式发布时会发生什么。

每个晚上在 master 上,以及每次提交被标记时,以下工作流将被执行。

这将大致自动

  1. 运行规范
  2. 构建夜间品牌的软件包
  3. 构建文档
  4. 从未签署的软件包构建 Docker 镜像
  5. 保留一份工件副本以供下载

在使用下一个版本标记提交之前,例如:0.25.0,我们要确保要标记的提交状态良好。如果在该提交中执行了夜间构建,那么我们就可以进行标记,但一种万无一失的方法是标记 0.25.0-pre1,并等待上述步骤完成。

一旦冒烟测试成功,0.25.0-pre1(或 -pre{N})的工件就可以进行标记 0.25.0

我们继续使用单个命令签署软件包并发布它们,以及文档和标记的 Docker 镜像。对于 Docker 镜像,一个新的 crystal:{version}-build 镜像也将被发布,以模仿夜间 -build 镜像。

还有两个待办事项

  1. 更新 Homebrew 公式
  2. 更新 CI 脚本以从刚刚发布的编译器构建下一个编译器。

好吧,还有几件事需要改进和完成。它永远不会结束!请查看下一节。

自动化发布积压工作

  • 更新 distribution-scripts 以生成 32 位二进制文件,以便二进制文件创建完全自动化。
  • 我们应该能够在 CI 中使用新的 Docker {version}-build 镜像。这将消除对 jhass 的 crystal build 镜像 的额外依赖,该镜像目前正在运行 CI。
  • 将发布签署的软件包的 Docker 镜像和文档的发布的步骤手动构建移至 circleci 中的作业。这些作业将保持待处理状态,等待 手动批准 信号,表明软件包已签署并发布。
  • 在编译器仓库中,有一些关于 Dockerfile 和 vagrantfile 的清理工作要做。这些很可能已经过时,一些以前的使用情况已经发生了变化。
  • 一个适当的 Linux 发行版夜间仓库将非常棒。目前有一个主要由 Travis 使用的仓库,以允许 crystal: stablecrystal: nightly 选项。将来,直接从 CI 自动更新夜间仓库将非常棒。
  • 我们还没有更新 OSX 发布过程中使用的 LLVM 版本。目前嵌入了一个自定义的 LLVM 3.9.1,因此提供的二进制文件比较小。但出于历史原因,在 LLVM 3.8 之前,我们被迫自定义构建以使用一些边缘功能。也许我们现在可以切换到官方的 LLVM 版本。自从我们开始从 omnibus 中分离以来,我们现在一直在使用第三方预编译的 LLVM 用于 Linux。如果您使用 OSX,您可能会注意到您的 LLVM 使用的是 4.0 或 5.0,因为您可能是从 Homebrew 安装了 Crystal。该编译器由 Homebrew 构建,他们使用的是最新的稳定版本。
  • Homebrew 公式补丁的创建可以自动化,因此提交新版本 PR 可以非常棒,更重要的是,它会让人难忘。
  • 可以为更多平台提供编译器,但前提是它可以自动化。最终,一个有意义的事情是提供一个版本的编译器,可以用于创建不同发行版的软件包,并在可能的情况下使用本机软件包。这样,每个发行版的 Crystal 编译器软件包都会更小,并且在声明依赖项方面会表现得更好 2

下一步

我们的下一个目标是研究如何提高编译器的性能。为此,我们将对语言语义进行一次修改,以解决我们知道存在的一些已知问题。这将使我们能够巩固语言,以便我们可以保证 1.0 之后没有重大变化。我们还有 1 月份剩下的 41 小时和 2 月份收到的捐款中剩下的 64 小时,我们将在 3 月份投入到这项工作中。请继续 支持我们的工作,这将产生巨大的影响!

  1. OSX CircleCI VM 中可用的 Ruby 版本有限。安装和构建特定版本的 Ruby 需要一些时间 https://discuss.circleci.com/t/cache-of-installed-ruby/19606  

  2. https://github.com/crystal-lang/crystal/issues/5650