Go 部落格

針對 Go 的套件版本規劃提案

Russ Cox
2018 年 3 月 26 日

前言

八年前,Go 團隊推出了 goinstall(導引至 go get),並推出了 Go 開發人員現今所熟悉的去中心化 URL 型匯入路徑。在我們發行 goinstall 後,人們提出的第一個問題之一便是如何整合版本資訊。我們承認我們不知道。很長一段時間以來,我們相信套件版本規劃的問題最適合由附加工具來解決,並鼓勵人們製作一個。Go 社群製作出許多採用不同方法的工具。每一個工具都幫助我們更能理解這個問題,但到了 2016 年中期,很明顯地已有了過多的解決方案。我們需要採用單一、官方的工具。

從 2016 年 7 月 GopherCon 開始社區討論後持續到秋季,我們都相信答案是採用 Rust 的 Cargo 所例證的套件版本控管方法,包含標記的語意版本、清單檔、鎖定檔,以及用於決定採用哪個版本的 SAT 求解器。Sam Boyer 率領團隊建立了遵循這個粗略計畫而成的 Dep,我們打算以此為整合 go 命令的範例。但隨著我們進一步了解 Cargo/Dep 方法的涵義,我清楚 Go 能從變更部分細節中獲益,特別是關於向後相容性。

相容性的影響

Go 1 最重要的新功能並非語言功能。而是 Go 1 對向後相容性的重視。在那之前,我們大約每個月會發布穩定版本快照,每次都進行重大的相容性變更。我們觀察到 Go 1 發布後立即顯著加速了興趣和採用率。我們認為,相容承諾讓開發人員更放心地依賴 Go 用於生產用途,這是 Go 今日受歡迎的主要原因。自 2013 年起,相容承諾一直鼓勵套件開發人員為自己的使用者提供類似的相容性預期。我們稱之為「匯入相容性規則」:「如果舊套件和新套件有相同匯入路徑,新套件必須向後相容於舊套件。」

獨立地,語意版本控管已成為許多語言社群 (包含 Go 社群) 中用於描述軟體版本的實際上的標準。使用語意版本控管時,較後版本的預期是向後相容於較早版本,但僅限於單一主要版本:v1.2.3 必須相容於 v1.2.1 和 v1.1.5,但 v2.3.4 不必相容於任何這些版本。

如果我們為 Go 套件採用語意版本控管,如同大多數的 Go 開發人員所預期,則匯入相容性規則要求不同的主要版本必須使用不同的匯入路徑。這個觀察使我們得到語意匯入版本控管,其中從 v2.0.0 起的版本在匯入路徑中包含主要版本:my/thing/v2/sub/pkg。

一年前,我強烈相信在匯入路徑中包含版本號碼在很大程度上是個人喜好問題,且我懷疑包含版本號碼特別優雅。但決定結果證明它不只關乎品味,還關乎邏輯:匯入相容性和語意版本控管需要語意匯入版本控管。當我意識到這一點時,邏輯上的必要性令我感到訝異。

我也驚訝地發現語意匯入版本控管有第二種獨立的邏輯路徑:漸進式程式碼修復或部分程式碼升級。在大型程式中,無法期待程式中的所有套件都能在同一時間從特定依賴項的 v1 更新至 v2。相反地,必須讓程式中的部分功能繼續使用 v1,而其他部分已升級至 v2。不過,程式的建置,以及程式的最終二進位檔,必須包含該依賴項的 v1 與 v2。讓它們有相同的匯入路徑會導致混淆,違反我們所謂的匯入唯一性規則:不同的套件必須有不同的匯入路徑。要進行部分程式碼升級、匯入唯一性,以及語意版本控管,唯一的方法就是採用語意匯入版本控管。

當然可以建立使用語意版本控管,但不用語意匯入版本控管的系統,但只能放棄部分程式碼升級或匯入唯一性。Cargo 透過放棄匯入唯一性來允許部分程式碼升級:在大型建置的不同部分中,給定的匯入路徑可以有不同的意義。Dep 透過放棄部分程式碼升級來確保匯入唯一性:大型建置中涉及的所有套件必須找到給定依賴項的單一協議版本,這會帶來大型程式無法建置的可能性。Cargo 堅持部分程式碼升級是正確的,這對大型軟體開發來說至關重要。Dep 堅持匯入唯一性也是正確的。複雜使用 Go 的當前供應支援可能會違反匯入唯一性。當這些情況發生時,由此產生的問題對於開發人員和工具來說理解起來相當困難。在部分程式碼升級和匯入唯一性之間進行選擇需要預測放棄哪一個會帶來更大的損害。語意匯入版本控管讓我們不必做出選擇,而可以維持兩者。

我亦驚訝地發現,匯入相容性大幅簡化了版本選取這個問題,即決定某次建置要使用何種套件版本。Cargo 與 Dep 的限制條件讓版本選取等同於 布林滿足性問題,這代表要確認是否真的存在有效版本組態是一件非常昂貴的事情。而且,可能會有許多有效的組態,卻沒有明確的準則能選出「最佳」的組態。相反地,仰賴匯入相容性可以讓 Go 使用簡單的線性時間演算法找到單一最佳組態,而且這個組態總是存在的。這個演算法是我稱為 最小版本選取 的演算法,它反過來消除了分割鎖檔和清單檔的需求。將它們改為單一的、簡短的組態檔,由開發人員與工具直接編輯,同時還能支援可重製建置。

我們使用 Dep 的經驗驗證了相容性的影響力。依照 Cargo 以及更早期的諸多系統的例子,我們在 Dep 中放棄了匯入相容性,以採用語意化版本。我不認為我們有刻意做出這個決定;我們只是跟隨了那些其他系統。透過親身體驗 Dep,我們更能理解允許不相容匯入路徑會創造多少的複雜性。藉由引入語意化匯入版本來重新制定匯入相容性規則,可以消除這種複雜性,進而產生更簡單的系統。

進度、雛形和提案

Dep 在 2017 年 1 月發布。它的基本模型,也就是標記了語意化版本的程式碼,以及一個指定依賴關係需求的組態檔,是相較於大多數 Go 版本供應工具的重大進步,而收斂成 Dep 本身也是明顯的進步。我全心全意鼓勵大家採用,尤其是幫助開發人員習慣思考 Go 套件版本,不管是用於自己的程式碼還是依賴關係。雖然 Dep 顯然讓我們朝著正確的方向前進,但我仍對於潛藏在細節中的複雜度惡魔心存疑慮。我特別擔心 Dep 缺乏在大型程式中逐步升級程式碼的支援。在 2017 年間,我與許多人討論過,包括 Sam Boyer 和套件管理工作小組的其他成員,但我們所有人都看不出簡化複雜性的明確方法。(我確實發現不少方法可以讓複雜性更形惡化。)眼看接近年底,SAT 求解器和不可滿足建置似乎仍然是我們能做到的最佳方法。

在十一月中旬,當再次嘗試找出 Dep 如何支援逐步程式碼升級時,我發現我們有關 import 相容性的舊建議暗示著語意 import 版本化。這看起來像是一個真正的突破。我寫了我的 語意 import 版本化 部落格文章的第一個草稿,最後提出建議,希望 Dep 能夠採用這個慣例。我把草稿傳送給曾經和我討論此事的人,並且引發了十分強烈的回應:所有人都喜歡或討厭它。我發現我需要在進一步傳播這個想法前,先理出更多語意 import 版本化的意涵,於是我著手去這麼做。

在十二月月中旬,我發現 import 相容性和語意 import 版本化可以共同將版本選取簡化為 最小版本選取。我寫了一個基礎實作,以確保自己了解這一點,花了一些時間學習它之所以如此簡單的理論,並且撰寫了一篇描述草稿的文章。即便如此,我仍然不確定這種方法是否可以在像 Dep 這樣的真實工具中實行。很明顯地需要一個雛型。

在 1 月,我開始製作一個簡單的 go 指令包裝程式,實作了語意 import 版本化和最小版本選取。瑣碎的測試運作良好。接近月底,我的簡單包裝程式能夠建置 Dep,一個利用了許多版本化套件的真實程式。這個包裝程式仍然沒有指令列介面,它建置 Dep 這項事實已經在幾個字串常數中硬寫入,但這方法顯然是可行的。

在 2 月的前三週,我將這個包裝程式轉變成一個完整的版本化 go 指令碼,vgo;撰寫 介紹 vgo 的部落格文章系列 草稿;並與 Sam Boyer、套件管理工作小組以及 Go 團隊討論它們。然後我花了 2 月的最後一週,終於與整個 Go 社群分享了 vgo 與它背後的想法。

除了匯入相容性、語意匯入版本化與最低版本選擇的核心概念之外,vgo 原型還導入許多較小但重要的變更,這些變更來自 goinstallgo get 八年來的經驗:Go 模組 的新概念,這是視為一個元件來版本化的套件集合;可驗證與已驗證的建置;以及 go 命運中全盤考量版本,這允許在 $GOPATH 外執行工作,並消除(大部分)vendor 目錄。

所有這些的結果就是 官方的 Go 建議,我在上週送出。儘管它看起來像是一個完整的實作,它仍只是一個原型,我們所有人都需要齊心協力才能完成它。您可以從 golang.org/x/vgo 下載並嘗試 vgo 原型,並可閱讀 版本化 Go 之遊覽,以了解使用 vgo 的感覺。

前方的路

我上週送出的建議確切來說就是:一個初始建議。我知道其中有一些問題是 Go 團隊與我看不出來的,因為 Go 開發人員會以我們不知道的許多聰明方式來使用 Go。建議回饋程序的目標是要讓我們所有人齊心協力辨識並解決目前建議中的問題,以確保在未來版本的 Go 中發佈的最終實作能順利運行在儘可能多的開發人員身上。請在 建議討論議題 上指出問題。我會隨著回饋意見的到來,持續更新 討論摘要常見問答集

為了讓這個建議奏效,整個 Go 生態系統(特別是今日主要的 Go 專案)都需要採取匯入相容性規則與語意匯入版本化。為了確保能順利發生,我們還將透過視訊會議對專案進行使用者回饋意見蒐集,這些專案對於如何將新的版本化建議納入其程式碼庫有疑問,或者有關於他們經驗的回饋。如果您有興趣參與這樣的會議,請透過 spf@golang.org 寄電子郵件給 Steve Francia。

我們相當期待(終於!)能為 Go 社群提供一個官方的單一解答,來回應如何將套件版本編碼納入 go get 的問題。感謝一路以來幫助我們走到這一步的每一個人,以及日後願意繼續協助我們的人。我們希望能在你們的幫助下,推出讓 Go 開發人員喜愛的產品。

下一篇文章:Go 的新品牌
前一篇文章:2017 年 Go 調查結果
網誌索引