做者:William Kennedy | 原文:Modules Part 01: Why And Whatgit
最近,我在嘗試整理一篇關於 Go 包管理髮展歷史的文章,但願能加深本身對這一塊知識的認識。在蒐集資料的時候,發現了這篇文章,順手翻譯了一下。github
本文是該系列的第一篇,主要介紹包依賴管理中一些基礎知識。文中提出了 Go 開發中的三個痛點,如何解決只能在 GOPATH 指定路徑開發,如何實現有效的版本管理,以及如何支持 Go 原生工具集依賴管理。針對它們,Go Module 都提供了相應的解決方案。緩存
從第一篇的內容上看,做者後面的文章應該會對 Go 的模塊機制進行詳細的剖析,很期待。話說,總感受這篇文章翻譯的有點彆扭,檢查的時候發現有好幾處語義理解錯誤,尷尬。bash
翻譯正文以下:服務器
Go Module 是 Go 爲包依賴管理提供的一個綜合性解決方案。從 Go 第一版發佈以來,Go 開發者針對包管理這一塊提出過三個痛點問題。ide
如何實如今 GOPATH 工做區以外進行代碼開發;工具
如何實現依賴版本化管理和有效識別出使用依賴的兼容性問題;測試
如何實現經過 Go 原生工具進行依賴管理;fetch
隨着 Go 1.13 的發佈,這三個問題都獲得瞭解決。在過去的兩年裏,Go 團隊成員爲此付出了巨大的努力。本文中將重點介紹從 GOPATH 到模塊機制的變化,還有模塊究竟解決了什麼問題。我將經過足夠易懂的語言向你們說明模塊的工做機制。
我以爲,重點要理解爲何模塊這樣工做。
GOPATH 是用於指定 Go 工做區的物理位置,一直以來都很好地服務着 Go 的開發者們。但它對非 Go 開發者並不友好,想在沒有任何配置的狀況下,隨時隨地進行 Go 開發,這是不可能的一件事。
Go 團隊要解決的第一個問題就是容許 Go 的源碼倉庫能被 clone 在磁盤中的任意位置,而不只僅是 GOPATH 指定的工做區。而且 Go 工具集仍然要能成功定位、編譯構建與測試它們。
上圖展現了一個 github 倉庫,ardanlabs/conf
,這個倉庫僅有一個包,它用於提供對應用配置處理的支持。
之前,若是想使用這個包,咱們須要經過 go get
並指定倉庫的規範化名稱實現下載一份到你的 GOPATH 下。倉庫規範化的名稱是由遠程倉庫的基礎 url 和倉庫名稱兩部分組成。
一個例子,在 Go Module 以前,若是你執行 go get github.com/ardanlabs/conf
,代碼將會被 clone 到 $GOPATH/src/github.com/ardanlabs/conf
目錄下。基於 GOPATH 和倉庫名,不管咱們把工做區設置何處,Go 工具集始終都能正確地找到代碼的位置。
清單 1
package conf_test
import (
...
"github.com/ardanlabs/conf"
...
)
複製代碼
清單1 顯示了 conf
包中測試文件 conf_test.go
中的導入其餘包的代碼片斷。
當測試包名用 _test
命名,這就意味着測試代碼和被測試代碼是在不一樣的包中,測試代碼必須導入要被測試的外部代碼。從上面的代碼片斷中,咱們能夠看出,測試代碼是如何將 conf 導入的。基於 GOPATH 機制,能夠很是容易地解析出導入包的路徑。而後,Go 工具集就能夠成功定位、編譯和測試代碼。
若是 GOPATH 不存在或者目錄結構與倉庫名稱不匹配,將會如何呢?
清單 2
import "github.com/ardanlabs/conf"
// GOPATH mode: Physical location on disk matches the GOPATH
// and Canonical name of the repo.
// GOPATH 模式:磁盤物理位置與 GOPATH 和倉庫的規範名稱相匹配
$GOPATH/src/github.com/ardanlabs/conf
// Module mode: Physical location on disk doesn’t represent
// the Canonical name of the repo.
// Module 模式:磁盤上的物理位置和倉庫全名沒有必然的匹配關係。
/users/bill/conf
複製代碼
清單2 展現了若是把倉庫 clone 到任意位置將會產生什麼問題。當開發者選擇將代碼下載他們但願的任意位置時,經過 import 包名稱解析出源碼的實際位置就不行了。
如何解決這個問題?
咱們能夠指定一個特殊的文件,使用它指定倉庫的規範名稱。這個文件的位置可理解爲是 GOPATH 的一個替代,在它其中定義了倉庫的規範名稱,Go 工具能夠經過這個名稱解析源碼中導入包的位置,而沒必要關心倉庫被 clone 到了什麼地方。
咱們把這個特殊的文件命名爲 go.mod
,將在這個文件中定義的由規範名稱表示的新實體稱爲 Module。
清單 3
module github.com/ardanlabs/conf
複製代碼
清單3 中顯示了 conf
倉庫中的 go.mod
文件的第一行 。
這一行定義了模塊的名稱,它同時也表明了倉庫全名,開發者期待使用它來引用庫中任意部分的代碼。如今,庫被下載到什麼位置已經再也不那麼重要了,Go 工具集會根據 module 文件所在位置和模塊名定位和解析內部包的導入,好比前面的示例中,在測試文件中的導入 conf
包。
如今,模塊機制容許咱們將代碼下載到任意位置。那下一個要解決的問題就是如何將代碼捆綁到一塊兒進行版本控制。
多數的版本管理系統都支持了在任意提交點打標籤。這些標籤一般是被用來發布新特性(v1.0.0、v2.3.8,等等),並且通常都是不可變的。
圖中顯示,conf
已經被打了三個不一樣的版本標籤。這三個標籤遵循着語義化版本的格式。
利用版本管理工具,咱們能夠經過指定 tag 實現 clone 任意版本的 conf
包的目的。但這有兩個問題亟待解決。
一旦回答完這兩個問題,又會產生第三個問題:
接着,狀況變得更差。
爲了要使用特定版本的 conf
包,你必需要下載 conf
的全部依賴。對於全部存在依賴傳遞的項目,這是一個共性的問題。
在 GOPATH 模式下,可使用 go get
識別和下載全部的依賴包,而後放到 GOPATH 指定的工做區下。但這不是一個完美的方案,由於 go get
僅僅只能從 master
分支下載和更新最新的代碼。當初期寫代碼時,從 master
下載代碼沒什麼問題。但幾個月後,有些依賴可能已經升級了,master
分支的最新代碼可能已經再也不兼容你的項目。這是由於你的項目沒有遵照明確的版本管理,任何的升級均可能帶來一個不兼容的改變。
在 Module 模式下,經過 go get
下載全部的依賴到一個單一的工做區再也不是首選方式。你須要一種方式實現爲整個項目中的每一個依賴指定一個兼容版本。同時,還要支持針對同一個依賴不一樣主版本的引入,以防止出現一個項目中依賴同一個包的不一樣主版本。
針對上面的這些問題,社區已經開發了一些解決方案,如 dep, godep, glide 等。但 Go 須要一個集成的解決方案。這個方案經過重用 go.mod 文件實現按版本維護這些直接和間接依賴。而後,將任何一個版本的依賴當成一個不可變的代碼包。這個特定版本不可變的代碼包被稱爲一個 Module。
上圖顯示了倉庫和模塊的關係。它顯示瞭如何引用到一個特定版本模塊中的包。在這種狀況下,在 conf-1.1.0
的代碼從版本爲 0.3.1
的 go-cmp
導入了 cmp
包。既然,依賴信息已經在 conf
模塊中(保存在模塊文件中),Go 就能夠經過內置的工具集獲取指定版本的模塊進行編譯構建。
一旦有了模塊,許多便利的工程體驗就體現了出來:
在這方面是很是值得慶幸地,由於在 Go 1.13 中,Go 團隊已經提供了許多這方面的支持。
這篇文章嘗試爲後面討論 Go 模塊是什麼以及 Go 團隊如何設計了這個方案打下了基礎。接下來還有一些問題須要討論,好比:
在接下來的文章中,我計劃將針對這些問題提供一個更深度的理解。如今,你要確保本身已經明白了倉庫、包和模塊之間的關係。