Go 部落格
Go 包版本控制提案
引言
八年前,Go 團隊推出了 goinstall
(後來演變為 go get
),並隨之引入了 Go 開發者今天熟悉的去中心化、類似 URL 的匯入路徑。在我們釋出 goinstall
後,人們提出的首要問題之一是如何整合版本資訊。我們承認當時並不知道答案。很長一段時間以來,我們認為包版本控制問題最好透過附加工具來解決,並鼓勵人們創造這樣的工具。Go 社群開發了許多采用不同方法的工具。每種工具都幫助我們更好地理解了這個問題,但到 2016 年中期,很明顯解決方案已經太多了。我們需要採用一個單一的官方工具。
在 2016 年 7 月的 GopherCon 大會開始並持續到秋季的社群討論之後,我們都認為答案將是遵循 Rust 的 Cargo 所代表的包版本控制方法,即帶有標籤的語義版本、一個清單檔案(manifest)、一個鎖定檔案(lock file)和一個 SAT 求解器 來決定使用哪些版本。Sam Boyer 帶領一個團隊建立了 Dep,該工具遵循了這個大致計劃,我們原本打算將其作為 go
命令整合的模型。但是,隨著我們對 Cargo/Dep 方法含義的瞭解深入,對我而言,Go 顯然可以從修改一些細節中受益,尤其是在向後相容性方面。
相容性的影響
Go 1 最重要的新特性不是語言特性,而是 Go 1 對向後相容性的強調。在此之前,我們大約每月釋出一次穩定的版本快照,每個版本都有重大的不相容變更。我們觀察到 Go 1 釋出後,關注度和採用率顯著加速。我們相信,相容性承諾 讓開發者對在生產環境中使用 Go 感到更加放心,這也是 Go 今天如此流行的關鍵原因。自 2013 年以來,Go FAQ 一直鼓勵包開發者為其使用者提供類似的相容性預期。我們將此稱為*匯入相容性規則*(import compatibility rule):“如果舊包和新包具有相同的匯入路徑,則新包必須與舊包向後相容。”
此外,語義版本控制 已成為許多語言社群(包括 Go 社群)中描述軟體版本的*事實標準*(de facto)。使用語義版本控制,後續版本預計將與早期版本向後相容,但僅限於同一主版本號內:v1.2.3 必須與 v1.2.1 和 v1.1.5 相容,但 v2.3.4 不需要與其中任何一個相容。
如果像大多數 Go 開發者所期望的那樣,我們為 Go 包採用語義版本控制,那麼匯入相容性規則要求不同的主版本必須使用不同的匯入路徑。這一觀察結果使得我們提出了*語義匯入版本控制*(semantic import versioning),其中從 v2.0.0 開始的版本在匯入路徑中包含主版本號:my/thing/v2/sub/pkg
。
一年前,我堅信是否在匯入路徑中包含版本號很大程度上是個人偏好問題,並且我懷疑這樣做是否特別優雅。但事實證明,這個決定並非關乎偏好,而是關乎邏輯:匯入相容性和語義版本控制共同要求語義匯入版本控制。當我意識到這一點時,這種邏輯必然性令我感到驚訝。
令我驚訝的是,我還意識到通往語義匯入版本控制的第二條獨立的邏輯路徑是:漸進式程式碼修復 或部分程式碼升級。在一個大型程式中,期望程式中的所有包同時從某個依賴的 v1 更新到 v2 是不現實的。相反,必須允許程式的一部分繼續使用 v1,而另一部分已升級到 v2。但這樣一來,程式的構建及其最終的可執行檔案就必須同時包含該依賴的 v1 和 v2。如果它們使用相同的匯入路徑,就會導致混淆,違反我們可能稱之為*匯入唯一性規則*(import uniqueness rule)的原則:不同的包必須有不同的匯入路徑。實現部分程式碼升級、匯入唯一性*以及*語義版本控制的唯一方法是也採用語義匯入版本控制。
當然,構建使用語義版本控制但不使用語義匯入版本控制的系統是可能的,但這隻能透過放棄部分程式碼升級或匯入唯一性來實現。Cargo 透過放棄匯入唯一性來允許部分程式碼升級:在大型構建的不同部分,同一個匯入路徑可以有不同的含義。Dep 透過放棄部分程式碼升級來確保匯入唯一性:大型構建中涉及的所有包都必須找到給定依賴的單一、商定的版本,這可能會導致大型程式無法構建。Cargo 堅持部分程式碼升級是正確的,這對於大規模軟體開發至關重要。Dep 堅持匯入唯一性同樣是正確的。Go 當前 vendoring(供應商依賴)支援的複雜用法可能會違反匯入唯一性。當這種情況發生時,由此產生的問題對於開發者和工具來說都非常難以理解。在部分程式碼升級和匯入唯一性之間做出選擇,需要預測放棄哪一個會帶來更大的痛苦。語義匯入版本控制讓我們避免了這種選擇,並同時保留了兩者。
令我驚訝的是,我發現匯入相容性極大地簡化了版本選擇,版本選擇是決定給定構建使用哪些包版本的問題。Cargo 和 Dep 的限制使得版本選擇等同於解決布林可滿足性問題,這意味著確定一個有效的版本配置是否存在都可能非常耗時。而且可能存在許多有效的配置,但沒有明確的標準來選擇“最佳”的一個。相反,依賴匯入相容性可以讓 Go 使用一個簡單的、線性時間演算法來找到唯一最佳的配置,並且這個配置總是存在。我將這個演算法稱為*最小版本選擇*(minimal version selection),它反過來消除了對單獨的鎖定檔案和清單檔案的需求。它用一個單一的、簡短的配置檔案取而代之,這個檔案可以直接由開發者和工具編輯,並且仍然支援可重現的構建。
我們使用 Dep 的經驗證明了相容性的影響。遵循 Cargo 和早期系統的思路,我們在設計 Dep 時,作為採用語義版本控制的一部分,放棄了匯入相容性。我不認為這是我們故意決定的;我們只是跟隨了那些其他系統。親身使用 Dep 的經驗幫助我們更好地理解了允許不相容匯入路徑會產生多少複雜性。透過引入語義匯入版本控制來恢復匯入相容性規則,消除了這種複雜性,從而形成了一個更簡單的系統。
進展、原型和提案
Dep 於 2017 年 1 月釋出。其基本模型——帶有語義版本標籤的程式碼,以及指定依賴需求的配置檔案——相對於大多數 Go 的 vendoring 工具來說,是一個明顯的進步,並且集中使用 Dep 本身也是一個明顯的進步。我衷心鼓勵大家採用它,特別是幫助開發者習慣於思考 Go 包的版本,無論是對於自己的程式碼還是依賴。儘管 Dep 顯然在朝著正確的方向推進我們,但我仍然對細節中的複雜性感到擔憂。我特別擔心 Dep 在大型程式中缺乏對漸進式程式碼升級的支援。在 2017 年全年,我與許多人進行了交流,包括 Sam Boyer 和包管理工作組的其他成員,但我們誰也看不出任何清晰的方法來降低複雜性。(我確實找到了許多增加複雜性的方法。)臨近年底時,似乎 SAT 求解器和不可滿足的構建可能是我們能做到的最好的了。
在 11 月中旬,我再次嘗試思考 Dep 如何支援漸進式程式碼升級時,我意識到我們關於匯入相容性的舊建議實際上暗示了語義匯入版本控制。這似乎是一個真正的突破。我寫下了後來成為我的語義匯入版本控制博文的初稿,結尾建議 Dep 採用這種約定。我將草稿傳送給了我一直在交流的人,它引起了非常強烈的反應:要麼非常喜歡,要麼非常討厭。我意識到我需要進一步研究語義匯入版本控制的更多影響,然後才能將這個想法傳播出去,於是我開始著手這項工作。
在 12 月中旬,我發現匯入相容性和語義匯入版本控制結合起來可以將版本選擇簡化為最小版本選擇。我寫了一個基本的實現來確保我理解它,花了一些時間學習它為何如此簡單的理論基礎,並寫了一篇描述它的博文草稿。儘管如此,我仍然不確定這種方法是否適用於像 Dep 這樣的真實工具。很明顯,需要一個原型。
一月份,我開始著手編寫一個簡單的 go
命令包裝器,它實現了語義匯入版本控制和最小版本選擇。簡單的測試執行良好。接近月底時,我的簡單包裝器可以構建 Dep,這是一個使用了許多版本化包的真實程式。這個包裝器仍然沒有命令列介面——構建 Dep 這個事實是硬編碼在幾個字串常量中的——但這種方法顯然是可行的。
我花了二月份的前三週將這個包裝器變成一個完整的版本化 go
命令,即 vgo
;撰寫介紹 vgo
的系列博文草稿;並與 Sam Boyer、包管理工作組和 Go 團隊討論這些內容。然後我在二月份的最後一週,終於向整個 Go 社群分享了 vgo
及其背後的思想。
除了匯入相容性、語義匯入版本控制和最小版本選擇這些核心思想之外,vgo
原型還引入了許多由 goinstall
和 go 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 的新品牌形象
上一篇文章:Go 2017 年度調查結果
部落格索引