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 時感到更加安心,這也是 Go 如今如此受歡迎的關鍵原因。自 2013 年以來,Go FAQ 一直鼓勵包開發者向用戶提供類似的相容性預期。我們稱之為匯入相容性規則:“如果一箇舊包和一個新包具有相同的匯入路徑,那麼新包必須向後相容舊包。”

與此同時,語義版本管理已成為許多語言社群(包括 Go 社群)描述軟體版本的事實標準。使用語義版本管理,預期後續版本在單一主版本內向後相容早期版本:v1.2.3 必須相容 v1.2.1 和 v1.1.5,但 v2.3.4 不需要相容其中任何一個。

如果我們將語義版本管理應用於 Go 包,正如大多數 Go 開發者所期望的那樣,那麼匯入相容性規則要求不同的主版本必須使用不同的匯入路徑。這一觀察使我們提出了語義匯入版本管理,其中 v2.0.0 及以上的版本在匯入路徑中包含主版本號:my/thing/v2/sub/pkg

一年前,我堅信是否在匯入路徑中包含版本號很大程度上是品味問題,並且我懷疑擁有它們是否特別優雅。但這個決定turns out to be a matter not of taste but of logic: 匯入相容性和語義版本管理共同要求語義匯入版本管理。當我意識到這一點時,這種邏輯上的必然性令我感到驚訝。

我也驚訝地發現,存在第二種獨立的邏輯途徑可以通向語義匯入版本管理:漸進式程式碼修復或部分程式碼升級。在一個大型程式中,不可能期望該程式中的所有包同時從某個依賴項的 v1 更新到 v2。相反,必須允許程式的一部分繼續使用 v1,而其他部分已升級到 v2。但那時,程式的構建以及程式的最終二進位制檔案必須同時包含該依賴項的 v1 和 v2。給它們相同的匯入路徑會導致混亂,違反我們可能稱之為匯入唯一性規則:不同的包必須有不同的匯入路徑。要實現部分程式碼升級、匯入唯一性語義版本管理,唯一的方法就是也採用語義匯入版本管理。

當然,可以構建使用語義版本管理而無需語義匯入版本管理的系統,但代價是放棄部分程式碼升級或匯入唯一性。Cargo 透過放棄匯入唯一性來允許部分程式碼升級:給定匯入路徑在大型構建的不同部分可能具有不同的含義。Dep 透過放棄部分程式碼升級來確保匯入唯一性:大型構建中涉及的所有包都必須找到一個單一的、一致同意的依賴項版本,這增加了大型程式可能無法構建的可能性。Cargo 在堅持部分程式碼升級方面是正確的,這對於大規模軟體開發至關重要。Dep 在堅持匯入唯一性方面同樣是正確的。Go 當前 vendoring 支援的複雜用法可能會違反匯入唯一性。當這種情況發生時,由此產生的問題對開發者和工具都帶來了極大的挑戰。在部分程式碼升級和匯入唯一性之間做出選擇,需要預測放棄哪個會更痛苦。語義匯入版本管理使我們避免了這個選擇,並兩者兼顧。

我也驚訝地發現,匯入相容性在多大程度上簡化了版本選擇,也就是為給定構建決定使用哪個包版本的難題。Cargo 和 Dep 的約束條件使得版本選擇等同於求解布林可滿足性,這意味著確定是否存在有效的版本配置可能非常昂貴。然後,可能存在許多有效的配置,但沒有明確的標準來選擇“最佳”配置。相反,依賴於匯入相容性可以讓 Go 使用一個簡單的、線性時間演算法來找到總是存在的、唯一的最佳配置。這個演算法,我稱之為最小版本選擇,反過來消除了對單獨的鎖定和清單檔案的需求。它用一個單一的、簡短的配置檔案替換了它們,該檔案由開發者和工具直接編輯,同時仍然支援可重複構建。

我們使用 Dep 的經驗證明了相容性的影響。遵循 Cargo 和早期系統的領導,我們在採用語義版本管理時,設計 Dep 時放棄了匯入相容性。我不認為我們是故意這樣做的;我們只是遵循了其他系統。使用 Dep 的第一手經驗幫助我們更清楚地瞭解了允許不相容的匯入路徑會帶來多少複雜性。透過引入語義匯入版本管理來恢復匯入相容性規則,消除了這種複雜性,從而得到一個更簡單的系統。

進展、一個原型和一個提案

Dep 於 2017 年 1 月釋出。它的基本模型——用語義版本標記的程式碼,以及一個指定依賴項需求的配置檔案——相比大多數 Go vendoring 工具是一個明顯的進步,而最終統一到 Dep 本身也是一個明顯的進步。我全心全意地鼓勵大家採用它,尤其是幫助開發者習慣於考慮 Go 包版本,無論是他們自己的程式碼還是他們的依賴項。雖然 Dep 顯然在將我們推向正確的方向,但我對細節中的複雜性問題一直有些擔憂。我特別擔心 Dep 缺乏對大型程式漸進式程式碼升級的支援。在 2017 年的整個過程中,我與許多人進行了交談,包括 Sam Boyer 和其他包管理工作組成員,但我們都沒有看到任何明顯的方法可以降低複雜性。(我確實找到了許多增加複雜性的方法。)到了年底,SAT 求解器和無法滿足的構建似乎仍然是我們所能做到的最好。

11 月中旬,我再次嘗試解決 Dep 如何支援漸進式程式碼升級的問題,我意識到我們關於匯入相容性的舊建議暗含了語義匯入版本管理。這似乎是一個真正的突破。我寫了第一稿,後來成為了我的語義匯入版本管理博文,並在結尾建議 Dep 採用該約定。我將草稿傳送給了我一直在交談的人,並得到了非常強烈的回應:每個人都喜歡它或討厭它。我意識到,在進一步傳播這個想法之前,我需要解決更多語義匯入版本管理的含義,於是我開始著手去做。

12 月中旬,我發現匯入相容性和語義匯入版本管理結合起來可以使版本選擇簡化為最小版本選擇。我寫了一個基本的實現來確保我理解它,花了一些時間學習它為什麼如此簡單的理論,並寫了一篇描述它的博文草稿。即便如此,我仍然不確定這種方法在 Dep 這樣的實際工具中是否可行。顯然需要一個原型。

1 月份,我開始著手開發一個簡單的 go 命令包裝器,它實現了語義匯入版本管理和最小版本選擇。簡單的測試執行良好。到了月末,我的簡單包裝器就可以構建 Dep 了,這是一個使用了許多版本化包的實際程式。包裝器仍然沒有命令列介面——它構建 Dep 的事實硬編碼在幾個字串常量中——但這種方法顯然是可行的。

我花了 2 月份的前三週將包裝器變成一個完整的版本化 go 命令,名為 vgo;撰寫了一系列介紹 vgo 的博文的草稿;並與 Sam Boyer、包管理工作組和 Go 團隊進行了討論。然後,在 2 月份的最後一週,我終於將 vgo 及其背後的想法與整個 Go 社群分享了。

除了匯入相容性、語義匯入版本管理和最小版本選擇的核心思想外,vgo 原型還引入了許多較小但重要的更改,這些更改源於八年來使用 goinstallgo get 的經驗:一個名為Go 模組的新概念,它是一個作為一個單元進行版本管理的包集合;可驗證和已驗證的構建;以及貫穿 go 命令的版本感知,從而實現了 $GOPATH 之外的工作,以及(大多數)vendor 目錄的消除。

所有這些工作的成果就是我上週提交的官方 Go 提案。儘管它看起來可能是一個完整的實現,但它仍然只是一個原型,我們需要共同努力來完成它。您可以從golang.org/x/vgo下載並試用 vgo 原型,您可以閱讀版本化 Go 之旅來了解使用 vgo 是什麼樣的。

前進的道路

我上週提交的提案確實只是一個初步的提案。我知道 Go 團隊和我自己看不到其中的問題,因為 Go 開發者以我們不知道的許多巧妙的方式使用 Go。提案反饋過程的目標是讓我們共同努力,識別並解決當前提案中的問題,以確保將在未來 Go 版本中釋出的最終實現能夠很好地服務於儘可能多的開發者。請在提案討論 issue 中指出問題。隨著反饋的到來,我將保持討論摘要常見問題解答的更新。

為了使該提案成功,整個 Go 生態系統——尤其是當今主要的 Go 專案——將需要採納匯入相容性規則和語義匯入版本管理。為了確保這能夠順利進行,我們還將透過視訊會議與有問題的專案進行使用者反饋會議,討論如何將新的版本管理提案納入其程式碼庫,或者提供關於他們經驗的反饋。如果您有興趣參加這樣的會議,請傳送電子郵件至 Steve Francia,郵箱為 spf@golang.org。

我們期待(終於!)為 Go 社群提供一個單一的官方答案,來解決如何將包版本管理融入 go get 的問題。感謝所有幫助我們走到這一步的人,以及所有將繼續幫助我們的人。我們希望,在您的幫助下,我們能夠交付 Go 開發者喜愛的東西。

下一篇文章:Go 的新品牌
上一篇文章:Go 2017 年調查結果
部落格索引