Crystal 自動發布
簡介
正如我們在 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 版本(如果可能)的編譯器。
我們想要什麼?
我們的主要目標是
- 完全自動化建置發布二進位檔的流程,包括佈建建置它們的機器。
- 簡化發布流程,以便更頻繁、更輕鬆地發布。
- 持續測試以在實際發布之前發現生態系統中的中斷(包括最受歡迎的 shard,例如 DB、Kemal、Amber、Lucky 等)。
- 擁有統一的發布腳本(為了我們自己的理智)。
在嘗試實現這些目標的同時,我們還想解決一些關於整體 CI 的技術債
- 將所有平台的 CI 移至單一 CI 環境。
- 避免手動發布後步驟,例如發布 CI 的 Docker 映像。
- 盡可能更新和統一相依性的版本。
我們做了什麼,以及我們發現了哪些「驚喜」?
你好,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 位元,有幾種替代方案。
- 使用我們過去使用的 omnibus 腳本,但在 官方 Docker i386 映像 中執行它們,而不是 32 位元虛擬機器,因為 CircleCI 不提供 32 位元機器執行器。鑑於我們將保留 omnibus 腳本一段時間,這似乎是一條簡單的路。
- 製作一個 distribution-scripts 版本,該版本將從先前的 32 位元編譯器建置 32 位元編譯器。
- 製作一個 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:nightly
和 crystallang/crystal:nightly-build
Docker 映像。前者應該能夠編譯使用 stdlib 的應用程式,後者包含建置編譯器所需的 LLVM 和相依性。
在編譯器儲存庫中保留沿襲
每次發布新的 Crystal 編譯器時,下一個通常會建立在新版本之上。發布和封裝解決方案通常會保持不變。為了不遺失編譯器的沿襲,最好在編譯器本身的儲存庫中準確追蹤每個標籤的建置方式。為了實現這一點,我們讓 distribution-scripts 接收參數來設定應該使用哪個現有的 Crystal 編譯器來建置特定的新提交或標籤。
Crystal 儲存庫現在包含對特定發行版的 distribution-scripts 的參考,以用於每晚建置。由於關於先前編譯器的資訊現在在 CI 設定中說明,因此沿襲將從現在開始在儲存庫本身中提供。一個額外的好處是,未來的不同分支將不受限於使用「最新」版本。
標記發布建置
建置標記的提交的流程大多相同。這很好!這是更順暢的建置流程的展現。
一旦 CircleCI 建置了發布套件和文件,接下來是
- 從 CircleCI 建置下載成品。
- 簽署套件。
- 發布到儲存庫。
- 文件需要上傳到 S3,並且新的目錄會被標記為最新。基本上與目前在 Travis 上執行的相同,但在發布套件時會一次性觸發。
- 建置一個包含已簽署套件的 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 被標記時,都會執行以下工作流程。
這大致會自動執行
- 執行規格測試
- 建置 nightly 品牌的套件
- 建置文件
- 從未簽署的套件建置 Docker 映像檔
- 保留一份可供下載的成品
在用下一個版本標記 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 映像檔。
有兩個待處理的手動步驟
- 更新 homebrew 公式
- 更新 ci 腳本,以從剛發布的版本建置下一個編譯器。
嗯,還有一些事情需要改進和完成。永無止境!請查看以下章節。
自動化發布待辦事項
- 更新 distribution-scripts 以發出 32 位元的二進位檔,以便完全自動化二進位檔的建立。
- 我們應該能夠在 CI 中使用新的 Docker {version}-build 映像檔。這將消除之前運行 CI 的 jhass 的 Crystal build 映像檔的額外依賴性。
- 將手動建置步驟(用於發布包含已簽署套件的 Docker 映像檔以及發布文件)移至 circleci 中的任務。這些任務將保持暫停狀態,等待手動批准的信號,表明套件已簽署並發布。
- 編譯器儲存庫中有一些關於 dockerfile 和 vagrantfile 的清理工作要做。這些很可能已過時,並且之前的一些用例已變更。
- 為 Linux 發行版建立一個適當的 nightly 儲存庫會很棒。有一個主要由 Travis 使用的儲存庫,以允許
crystal: stable
和crystal: 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 月投入到這方面。請繼續支持我們的工作,這會產生巨大的差異!
-
OSX CircleCI VM 中可用的 Ruby 版本有限。安裝和建置特定的 Ruby 版本需要一些時間 https://discuss.circleci.com/t/cache-of-installed-ruby/19606 ↩