跳至內容
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 中執行 Linux 和 Linux 32 位元,使用 Docker 映像來建置編譯器,而 Mac 建置則依賴 CircleCI。順帶一提,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 設定(僅測試 OSX)遷移到 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 版本)。因此,我們手動為 32 位元建置了 Crystal 0.24.2,並使用較新的 GC 版本。這將允許我們在未來使用 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 版本時做了一些測試,並在發布之前發現了版本中的一些問題。但遺憾的是,我們在標記後才發現這些問題。隨著 nightly 套件的推出和標記流程的一些改進,我們希望在發布前執行一些冒煙測試。這將幫助我們更早地檢測到問題,或許可以向受影響的儲存庫提交 PR/issue。雖然編譯器和標準函式庫有大量的規格,但肯定在野外存在一些有趣的場景。

今天,我們使用這些生態系統測試來檢查(在 Darwin、Linux 和 Linux32 上)是否可以執行一些基本命令、堆疊追蹤是否有效、crystal-db 和驅動程式是否有效,以及網頁框架 Amber、Lucky 和 Kemal 是否能夠運作。

這並不完美,但至少是努力預先知道即將發布的版本中可能會出現問題。

從現在開始我們將如何做事?

讓我們回顧一下,每天晚上和正式發布時會發生什麼。

每天晚上在 master 分支上,以及每次 commit 被標記時,都會執行以下工作流程。

這大致會自動執行

  1. 執行規格測試
  2. 建置 nightly 品牌的套件
  3. 建置文件
  4. 從未簽署的套件建置 Docker 映像檔
  5. 保留一份可供下載的成品

在用下一個版本標記 commit 之前,例如:0.25.0,我們希望確保要標記的 commit 狀態良好。如果該 commit 中執行了 nightly,我們就沒問題,但一個安全的做法是標記 0.25.0-pre1 並等待上述步驟。

一旦冒煙測試成功,0.25.0-pre1(或 -pre{N})的成品就可以順利標記為 0.25.0

我們繼續簽署套件,並使用單一命令將它們與文件和標記的 Docker 映像檔一起發布。對於 Docker 映像檔,也會發布新的 crystal:{version}-build 映像檔,以模擬 nightly 的 -build 映像檔。

有兩個待處理的手動步驟

  1. 更新 homebrew 公式
  2. 更新 ci 腳本,以從剛發布的版本建置下一個編譯器。

嗯,還有一些事情需要改進和完成。永無止境!請查看以下章節。

自動化發布待辦事項

  • 更新 distribution-scripts 以發出 32 位元的二進位檔,以便完全自動化二進位檔的建立。
  • 我們應該能夠在 CI 中使用新的 Docker {version}-build 映像檔。這將消除之前運行 CI 的 jhass 的 Crystal build 映像檔的額外依賴性。
  • 將手動建置步驟(用於發布包含已簽署套件的 Docker 映像檔以及發布文件)移至 circleci 中的任務。這些任務將保持暫停狀態,等待手動批准的信號,表明套件已簽署並發布。
  • 編譯器儲存庫中有一些關於 dockerfile 和 vagrantfile 的清理工作要做。這些很可能已過時,並且之前的一些用例已變更。
  • 為 Linux 發行版建立一個適當的 nightly 儲存庫會很棒。有一個主要由 Travis 使用的儲存庫,以允許 crystal: stablecrystal: nightly 選項。未來,最好直接從 CI 自動更新 nightly 儲存庫。
  • 我們仍未更新 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