傳統(tǒng)Go構(gòu)建以及包依賴管理
- Go在構(gòu)建設(shè)計(jì)方面深受Google內(nèi)部開發(fā)實(shí)踐的影響贺拣,比如go get的設(shè)計(jì)就深受 Google內(nèi)部單一代碼倉庫(single monorepo)和基于主干(trunk/mainline based)的開發(fā)模型 的影響:只獲取Trunk/mainline代碼和版本無感知
- 我們知道go get獲取的代碼會(huì)放在GOROOT/src下面纵柿,而go build會(huì)在GOROOT/src和GOPATH/src下面按照import path去搜索package昂儒,由于go get 獲取的都是各個(gè)package repo的trunk/mainline的代碼委可,因此,Go 1.5之前的Go compiler都是基于目標(biāo)Go程序依賴包的trunk/mainline代碼去編譯的拾酝。這樣的機(jī)制帶來的問題是顯而易見的卡者,至少包括:
- 因依賴包的trunk的變化崇决,導(dǎo)致不同人獲取和編譯你的包/程序時(shí)得到的結(jié)果實(shí)質(zhì)是不同的底挫,即不能實(shí)現(xiàn)reproduceable build
- 因依賴包的trunk的變化脸侥,引入不兼容的實(shí)現(xiàn),導(dǎo)致你的包/程序無法通過編譯
- 因依賴包演進(jìn)而無法通過編譯官边,導(dǎo)致你的包/程序無法通過編譯
- 為了實(shí)現(xiàn)reporduceable build外遇,Go 1.5引入了Vendor機(jī)制臀规,Go編譯器會(huì)優(yōu)先在vendor下搜索依賴的第三方包,這樣如果開發(fā)者將特定版本的依賴包存放在vendor下面并提交到code repo玩徊,那么所有人理論上都會(huì)得到同樣的編譯結(jié)果谨究,從而實(shí)現(xiàn)reporduceable build
Go語言版本控制
在沒有版本控制時(shí),go get 的一個(gè)重大缺陷是對(duì)于給定的更新無法知道是否是用戶所期望的畔塔。
Go 將在下個(gè)版本(1.11)中看到官方的包版本控制鸯屿,去除了 GOPATH 依賴寄摆,同時(shí)還引入了 module(模塊) 的概念。
版本控制可以讓我們能夠?qū)崿F(xiàn)重編譯桑阶。當(dāng)我讓你試用我程序最新版本時(shí)勾邦,我清楚的知道你不僅僅獲取到的是我最新程序的代碼,還包括我代碼所依賴的相同版本的包萎河,這樣才能編譯出完全一樣的二進(jìn)制包。
版本控制還能讓我們不同階段保持同樣的編譯方式换可,即使我們的依賴包可能有新版本了厦幅,只要我們的配置未允許使用,go 命令也不會(huì)使用新版本的包译荞。
盡管添加版本控制是必須的功能吞歼,但同時(shí)也不能失去go 命令行現(xiàn)有的優(yōu)秀特性:簡(jiǎn)單塔猾、高效、易懂糯俗,所以它應(yīng)該足夠透明不能破壞掉go get 本身功能睦擂。
Go新版本中保留了go get的精華部分顿仇,增加了重復(fù)構(gòu)建,采用了語義化的版本控制鸿吆,棄用了 vendor,廢棄了基礎(chǔ)工程創(chuàng)建時(shí)依賴GOPATH述呐,并且提供了老項(xiàng)目平滑遷移的方式伞剑。
Go添加版本控制共分四個(gè)步驟
導(dǎo)入兼容規(guī)則
-
包管理系統(tǒng)中最大的痛苦在于解決兼容性問題
比如,大多數(shù)系統(tǒng)中包B 聲明需要的包D 版本是6或者更高版本市埋,然后包C聲明所需的包D 版本是2,3和4,但不能高于版本5恕刘。如果你正在編寫包A 缤谎,你想同時(shí)引入包B 和C ,那么你不走運(yùn)了:沒有一個(gè)獨(dú)立的D 版本可以供B 和C 同時(shí)選擇編譯進(jìn)A褐着。B 和C 做的都是合理的坷澡,你也沒辦法改變它,所以你就被卡住了含蓉。
-
為了避免主導(dǎo)者設(shè)計(jì)一個(gè)導(dǎo)致現(xiàn)有的大型程序無法編譯的系統(tǒng)项郊,提案要求包作者遵循以下導(dǎo)入兼容性原則:
如果一個(gè)舊包和新包有相同的導(dǎo)入路徑,新包必須向后兼容舊包 這條規(guī)則是對(duì)前面 Go FAQ 的重申斟赚,引用 FAQ 中最后講的:“如果需要完全變更着降,那么就創(chuàng)建個(gè)新導(dǎo)入路徑的包”。開發(fā)者希望能通過語義化的版本來表達(dá)這樣一個(gè)變更拗军,因此我們把語義化版本控制也加入到我們提案中任洞。具體點(diǎn)說,主版本2 和更新的版本可以通過在路徑中包含版本信息來區(qū)分发侵,比如:
import "github.com/go-yaml/yaml/v2"
- 包作者遵循導(dǎo)入兼容性原則可以讓我們減少適配工作交掏,讓系統(tǒng)更簡(jiǎn)單的同時(shí)也讓包生態(tài)減少碎片化
當(dāng)然,實(shí)際上盡管作者盡最大努力去做了刃鳄,更新時(shí)也難免會(huì)出現(xiàn)破壞用戶使用的情況盅弛。因此,使用一個(gè)不頻繁升級(jí)的升級(jí)機(jī)制很重要叔锐,這也是接下來我們要講的挪鹏。
最小版本規(guī)則
-
幾乎現(xiàn)在所有的包管理包括dep和cargo都在構(gòu)建時(shí)使用最新的包版本,基于兩方面的重要因素掌腰,被認(rèn)為這是個(gè)錯(cuò)誤的約定:
首先狰住,“最新可用版本”有可能因?yàn)橥獠渴录?dǎo)致變更,像新版本發(fā)布齿梁。也許今晚你依賴的包中有人會(huì)發(fā)布個(gè)新版本催植,第二天早上你再編譯有可能就產(chǎn)生不同的結(jié)果了;
第二勺择,為了覆蓋這個(gè)默認(rèn)約定创南,開發(fā)者花費(fèi)大量的時(shí)間告訴包管理器不使用哪個(gè)版本的包。
-
提案中我們使用了不同的方式省核,稱之為最小版本選擇稿辙。
構(gòu)建時(shí)每個(gè)包默認(rèn)使用的是最老的可用版本,這個(gè)方式讓昨天和今天的編譯不會(huì)有變化气忠,因?yàn)槟憧偛粫?huì)在今天發(fā)布一個(gè)更老版本吧邻储。更好的是,開發(fā)者只需告訴包管理器最小可用的那個(gè)版本旧噪,包管理器就可以很快的決定哪個(gè)版本可用吨娜。我們稱它為最小版本選擇一方面是因?yàn)槲覀冞x擇的是最小版本,另一方面是因?yàn)閷?duì)整個(gè)系統(tǒng)來說是最小化的淘钟,避免了現(xiàn)有系統(tǒng)的復(fù)雜性宦赠。
最小版本選擇為模塊指定了其依賴模塊的最低版本需求,這為后續(xù)升級(jí)和降級(jí)操作提供了一個(gè)很好的選擇。同時(shí)勾扭,它還可以通過排除指定版本的依賴或者指定特殊版本依賴完成編譯毡琉。
最小版本選擇在不鎖定文件情況下默認(rèn)就完成了可重復(fù)構(gòu)建。
最小版本選擇是導(dǎo)入兼容的關(guān)鍵妙色。用戶不會(huì)再說:“不桅滋,版本太新了”,更多情況是面臨“不燎斩,版本太舊了”虱歪,這種情況下解決方案很明確:升級(jí)新版本就可以了。
Go Module
-
Go Module是共享一個(gè)導(dǎo)入路徑前綴的包集合栅表,也就是我們所說的模塊路徑
Module是版本控制的單元笋鄙,Module的版本通過語義化的版本字符串表示,當(dāng)開發(fā)中使用Git 時(shí)怪瓶,開發(fā)者通過給模塊的Git 資源庫添加一個(gè)新tag的方式來定義一個(gè)新的語義化版本萧落。盡管強(qiáng)烈推薦使用語義化版本的方式,但也支持指向特定commit洗贰。
-
模塊定義在一個(gè)叫g(shù)o.mod的新文件里找岖,里面包含了模塊所依賴包的最小版本
下面就是個(gè)簡(jiǎn)單的go.mod文件:
module "rsc.io/hello"
require (
"golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54
"rsc.io/quote" v1.5.2
)
這個(gè)文件通過路徑標(biāo)識(shí) rsc.io/hello 定義了一個(gè)模塊,它本身還依賴于兩個(gè)其他模塊:golang.org/x/text 和 rsc.io/quote 敛滋,這個(gè)模塊自身編譯的時(shí)候使用的是 go.mod 文件中指定的依賴列表的版本许布。對(duì)于更上一層的編譯,其他導(dǎo)入這個(gè)模塊的地方將使用它較新的版本編譯绎晃。
包發(fā)布者最好使用語義化的 tag 發(fā)布版本蜜唾,vgo 也鼓勵(lì)通過打tag的版本號(hào)方式,而不是任意的提交版本庶艾。
除了指定必須的依賴版本袁余,go.mod 文件還可以實(shí)現(xiàn)前面章節(jié)中提到的排除和替換的版本,但是這些只有當(dāng)直接編譯該模塊的時(shí)候起作用咱揍,在模塊作為整體工程一部分編譯時(shí)就不行了
Goinstall 和舊的 go get 通過像git 和hg 這樣的版本控制工具直接下載代碼颖榜,這種方式存在很多問題,其中包括碎片化嚴(yán)重:用戶如果沒有bzr 就沒法下載托管在Bazaar 資源庫的代碼煤裙。相比之下掩完,Go Module則是通過HTTP 下載zip 包的方式。
Module統(tǒng)一通過zip包的形式提供可以讓下載協(xié)議更簡(jiǎn)單硼砰,公司或者個(gè)人可以處于任何原因考慮(安全或者想要緩存副本防止源被刪除)自己做下載代理藤为,使用代理來確保可用性并且通過go.mod定義了哪些代碼需要用到
Go 命令
go 命令必須更新才能使用模塊功能夺刑。一個(gè)重要的變化就是常用的構(gòu)建命令,像 gobuild, go install, go run, 和 go test 將需要按指定需求解析對(duì)應(yīng)的依賴關(guān)系了
最重要的變化還是終結(jié)了GOPATH作為Go 代碼工作空間的設(shè)置,由于go.mod文件包含了完整的模塊路徑并且還定義了每個(gè)使用的依賴的版本遍愿,因此包含go.mod文件的目錄就可以被認(rèn)為是一個(gè)目錄樹的根目錄了存淫,該目錄樹作用于自身的工作空間,并且和其他類似的目錄彼此隔離≌犹睿現(xiàn)在你只需git clone然后cd就可以直接擼代碼了桅咆,不再需要GOPATH了