這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文譯自 A Tour of Versioned Go (vgo), Go & Versioning 的第 2 部分, 著作權@歸原文所有.
對我而言, 設計意味著構建, 拆除和再次構建, 一遍又一遍.
為了編寫新的版本控制提案, 我構建了一個原型 vgo
, 來處理許多細微的細節. 這篇博文展示了如何使用 vgo
.
你現在可以通過運行 go get golang.org/x/vgo
下載並嘗試 vgo
. Vgo
是 go
命令的一個直接替換(和分支拷貝).
你運行 vgo
而不是 go
, 它將使用你安裝在 $GOROOT
(Go 1.10 beta1 或更高版本) 的編譯器和標準庫.
隨著我們更多地瞭解什麼可行, 什麼不可行, vgo
的語義和命令列細節可能會發生變化.
但是, 我們打算避免 go.mod
檔案格式的向後不相容的更改, 以便今天添加了 go.mod
的項目以後也可以工作. 在我們完善提案時, 我們也會相應地更新 vgo
.
樣本
該部分示範怎麼使用 vgo
. 請按照步驟進行實驗.
從安裝 vgo
開始:
$ go get -u golang.org/x/vgo
你一定會遇到有趣的 bug
, 因為 vgo
現在最多隻有輕微的測試. 請使用 Go 問題跟蹤 進行 bug
上報, 標題以 "x/vgo" 開頭. 多謝.
Hello, world
我們來寫一個有趣的 "Hello, world" 程式. 在 GOPATH/src
目錄之外建立一個目錄並切換到它:
$ cd $HOME$ mkdir hello$ cd hello
然後建立一個 hello.go
:
package main // import "github.com/you/hello"import ( "fmt" "rsc.io/quote")func main() { fmt.Println(quote.Hello())}
或者下載它:
$ curl -sS https://swtch.com/hello.go >hello.go
建立一個空的 go.mod
檔案來標記此模組的根目錄, 然後構建並運行新程式:
$ echo >go.mod$ vgo buildvgo: resolving import "rsc.io/quote"vgo: finding rsc.io/quote (latest)vgo: adding rsc.io/quote v1.5.2vgo: finding rsc.io/quote v1.5.2vgo: finding rsc.io/sampler v1.3.0vgo: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0cvgo: downloading rsc.io/quote v1.5.2vgo: downloading rsc.io/sampler v1.3.0vgo: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c$ ./helloHello, world.$
注意這裡沒有顯式的需要運行 vgo get
. 普通的 vgo build
將在遇到未知匯入時尋找包含它的模組, 並將該模組的最新版本作為依賴添加到當前模組中.
運行任何 vgo
命令的一個副作用是必要時會更新 go.mod
. 這種情況下, vgo build
會寫入新的 go.mod
檔案:
$ cat go.modmodule "github.com/you/hello"require "rsc.io/quote" v1.5.2$
由於 go.mod
已寫入, 下一次 vgo build
將不會再次解析匯入或列印那麼多:
$ vgo build$ ./helloHello, world.$
即使明天發布了 rsc.io/quote v1.5.3
或 v1.6.0
, 該目錄中的構建仍將繼續使用 v1.5.2
, 除非進行明確的升級(見下文).
go.mod
檔案列舉了依賴的最小集合, 忽略了已列舉中所隱含的.
在這種情況下, rsc.io/quote v1.5.2
依賴特定版本的 rsc.io/sampler
和 golang.org/x/text
, 所以在 go.mod
中重複列舉它們是冗餘的.
使用 vgo list -m
仍然可以找到構建所需的全套模組:
$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0crsc.io/quote v1.5.2rsc.io/sampler v1.3.0$
此時你可能想知道為什麼我們簡單的 "hello world" 程式會使用 golang.org/x/text
.
實際上 rsc.io/quote
依賴 rsc.io/sampler
, 後者又依賴 golang.org/x/text
進行 language matching .
$ LANG=fr ./helloBonjour le monde.$
升級
我們已經看到, 當必須將新模組添加到構建以解決新的匯入時, vgo
會採用最新的模組.
此前, 它需要 rsc.io/quote
, 並發現 v1.5.2
是最新的. 但除瞭解析新的匯入, vgo
僅使用 go.mod
檔案中列出的版本.
在我們的例子中, rsc.io/quote
間接依賴於 golang.org/x/text
和 rsc.io/sampler
的特定版本.
事實證明, 這兩個軟體包都有較新的版本, 正如我們通過 vgo list -u
(檢查更新的軟體包)看到的那樣:
$ vgo list -m -uMODULE VERSION LATESTgithub.com/you/hello - -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2 (2018-02-14 10:44) -rsc.io/sampler v1.3.0 (2018-02-13 14:05) v1.99.99 (2018-02-13 17:20)$
這兩個軟體包都有更新的版本, 所以我們可能想在我們的 hello
程式中升級它們.
首先升級 golang.org/x/text
:
$ vgo get golang.org/x/textvgo: finding golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54vgo: downloading golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2)$
vgo get
命令將尋找給定模組的最新版本, 並通過更新 go.mod
來將該版本添加為當前模組的依賴.
從現在開始, 未來的構建將使用較新的 text
模組:
$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2rsc.io/sampler v1.3.0$
當然, 升級之後, 測試一切仍然工作良好是個好主意.
我們的依賴 rsc.io/quote
和 rsc.io/sampler
尚未使用較新的 text
模組進行測試. 我們可以在我們建立的配置中運行他們的測試:
$ vgo test all? github.com/you/hello [no test files]? golang.org/x/text/internal/gen [no test files]ok golang.org/x/text/internal/tag 0.020s? golang.org/x/text/internal/testtext [no test files]ok golang.org/x/text/internal/ucd 0.020sok golang.org/x/text/language 0.068sok golang.org/x/text/unicode/cldr 0.063sok rsc.io/quote 0.015sok rsc.io/sampler 0.016s$
在原版 go
命令中, 軟體包模式 all
意味著 GOPATH
中能找到的所有軟體包. 這幾乎總是太多而無用.
在 vgo
中, 我們已經將 all
的含義縮小為 "當前模組中的所有軟體包, 以及它們以遞迴方式匯入的軟體包". rsc.io/quote
模組的 1.5.2
版本包含一個 buggy
包:
$ vgo test rsc.io/quote/...ok rsc.io/quote (cached)--- FAIL: Test (0.00s) buggy_test.go:10: buggy!FAILFAIL rsc.io/quote/buggy 0.014s(exit status 1)$
然而, 除非我們模組中的某個包匯入 buggy
, 否則它是不相干的, 所以它不包含在 all
裡面. 無論如何, 升級的 x/text
看起來可以工作. 此時我們多半可以提交 go.mod
.
另一種選擇是使用 vgo get -u
升級構建所需的所有模組:
$ vgo get -uvgo: finding golang.org/x/text latestvgo: finding rsc.io/quote latestvgo: finding rsc.io/sampler latestvgo: finding rsc.io/sampler v1.99.99vgo: finding golang.org/x/text latestvgo: downloading rsc.io/sampler v1.99.99$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2 "rsc.io/sampler" v1.99.99)$
在這裡, vgo get -u
保留了升級後的 text
模組, 並將 rsc.io/sampler
升級到其最新版本 v1.99.99
.
讓我們來運行測試:
$ vgo test all? github.com/you/hello [no test files]? golang.org/x/text/internal/gen [no test files]ok golang.org/x/text/internal/tag (cached)? golang.org/x/text/internal/testtext [no test files]ok golang.org/x/text/internal/ucd (cached)ok golang.org/x/text/language 0.070sok golang.org/x/text/unicode/cldr (cached)--- FAIL: TestHello (0.00s) quote_test.go:19: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."FAILFAIL rsc.io/quote 0.014s--- FAIL: TestHello (0.00s) hello_test.go:31: Hello([en-US fr]) = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world." hello_test.go:31: Hello([fr en-US]) = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Bonjour le monde."FAILFAIL rsc.io/sampler 0.014s(exit status 1)$
看起來 rsc.io/sampler v1.99.99
出了問題. 果然:
$ vgo build$ ./hello99 bottles of beer on the wall, 99 bottles of beer, ...$
vgo get -u
擷取每個依賴的最新版本的行為正和 go get
下載所有不在 GOPATH
的包所做的一樣. 在一個 GOPATH
裡空無一物的系統上:
$ go get -d rsc.io/hello$ go build -o badhello rsc.io/hello$ ./badhello99 bottles of beer on the wall, 99 bottles of beer, ...$
重要的區別是, 預設情況下, vgo
不會以這種方式運行. 你也可以通過降級撤消它.
降級
要降級軟體包, 請使用 vgo list -t
顯示可用的標記(tag)版本:
$ vgo list -t rsc.io/samplerrsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99$
然後使用 vgo
擷取要求的特定版本, 例如 v1.3.1
:
$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2 "rsc.io/sampler" v1.99.99)$ vgo get rsc.io/[email protected]vgo: finding rsc.io/sampler v1.3.1vgo: downloading rsc.io/sampler v1.3.1$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2rsc.io/sampler v1.3.1$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2 "rsc.io/sampler" v1.3.1)$ vgo test all? github.com/you/hello [no test files]? golang.org/x/text/internal/gen [no test files]ok golang.org/x/text/internal/tag (cached)? golang.org/x/text/internal/testtext [no test files]ok golang.org/x/text/internal/ucd (cached)ok golang.org/x/text/language (cached)ok golang.org/x/text/unicode/cldr (cached)ok rsc.io/quote 0.016sok rsc.io/sampler 0.015s$
降級一個軟體包可能需要降級其他軟體包. 例如:
$ vgo get rsc.io/[email protected]vgo: finding rsc.io/sampler v1.2.0vgo: finding rsc.io/quote v1.5.1vgo: finding rsc.io/quote v1.5.0vgo: finding rsc.io/quote v1.4.0vgo: finding rsc.io/sampler v1.0.0vgo: downloading rsc.io/sampler v1.2.0$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.4.0rsc.io/sampler v1.2.0$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.4.0 "rsc.io/sampler" v1.2.0)$
在這種情況下, rsc.io/quote v1.5.0
是第一個需要 rsc.io/sampler v1.3.0
的版本; 早期版本只需要 v1.0.0
(或更高版本).
降級選擇了 rsc.io/quote v1.4.0
, 這是與 v1.2.0
相容的最新版本.
也可以通過指定 none
作為版本來完全刪除一個依賴, 這是一種極端的降級形式:
$ vgo get rsc.io/[email protected]vgo: downloading rsc.io/quote v1.4.0vgo: finding rsc.io/quote v1.3.0$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.3.0$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.3.0)$ vgo test allvgo: downloading rsc.io/quote v1.3.0? github.com/you/hello [no test files]ok rsc.io/quote 0.014s$
讓我們回到一切都是最新版本的狀態, 包括 rsc.io/sampler v1.99.99
:
$ vgo get -uvgo: finding golang.org/x/text latestvgo: finding rsc.io/quote latestvgo: finding rsc.io/sampler latestvgo: finding golang.org/x/text latest$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2rsc.io/sampler v1.99.99$
排除 (Excluding)
在確定 v1.99.99
並不適用於我們的 hello world
程式後, 我們可能想記錄下這個事實, 以避免將來出現問題.
我們可以通過向 go.mod
添加 exclude
指令來做到這一點:
exclude "rsc.io/sampler" v1.99.99
之後的動作表現的好像該模組不存在一樣:
$ echo 'exclude "rsc.io/sampler" v1.99.99' >>go.mod$ vgo list -t rsc.io/samplerrsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99 # excluded$ vgo get -uvgo: finding golang.org/x/text latestvgo: finding rsc.io/quote latestvgo: finding rsc.io/sampler latestvgo: finding rsc.io/sampler latestvgo: finding golang.org/x/text latest$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2rsc.io/sampler v1.3.1$ cat go.modmodule "github.com/you/hello"require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2 "rsc.io/sampler" v1.3.1)exclude "rsc.io/sampler" v1.99.99$ vgo test all? github.com/you/hello [no test files]? golang.org/x/text/internal/gen [no test files]ok golang.org/x/text/internal/tag (cached)? golang.org/x/text/internal/testtext [no test files]ok golang.org/x/text/internal/ucd (cached)ok golang.org/x/text/language (cached)ok golang.org/x/text/unicode/cldr (cached)ok rsc.io/quote (cached)ok rsc.io/sampler (cached)$
排除僅適用於當前模組的構建. 如果當前模組被更大的構建所依賴, 則排除不適用.
例如, rsc.io/quote
的 go.mod
中的排除不適用於我們的 "hello, world" 構建.
這一策略的權衡讓當前模組的作者幾乎可以任意控制自己的構建, 而不會受到它們依賴的模組幾乎任意控制的影響.
此時, 正確的下一步是聯絡 rsc.io/sampler
的作者並在 v1.99.99
中報告問題, 因此它可以在 v1.99.100
中修複. 不幸的是, 作者有一個博文依賴它而不予修複.
替換 (Replacing)
如果確實在依賴中發現了問題, 則需要一種方法將其暫時替換為一個合適的副本. 假設我們想改變一些關於 rsc.io/quote
的行為.
也許我們想要解決 rsc.io/sampler
中的問題, 或者我們想要做其他的事情. 第一步是使用通常的 git
命令檢出 quote
模組:
$ git clone https://github.com/rsc/quote ../quoteCloning into '../quote'...
然後編輯 ../quote/quote.go
來改變 func Hello
的一些內容. 例如, 我把它的傳回值從 sampler.Hello()
更改為 sampler.Glass()
, 這是一個更有趣的問候語.
$ cd ../quote$ <edit quote.go>$
改變了複製代碼之後, 我們可以通過向 go.mod
添加 replace
指令來讓我們的構建使用它來代替真正的構建:
replace "rsc.io/quote" v1.5.2 => "../quote"
然後我們可以使用它來構建我們的程式:
$ cd ../hello$ echo 'replace "rsc.io/quote" v1.5.2 => "../quote"' >>go.mod$ vgo list -mMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2 => ../quotersc.io/sampler v1.3.1$ vgo build$ ./helloI can eat glass and it doesn't hurt me.$
你也可以將一個不同的模組命名為替換模組. 例如, 你可以複製 github.com/rsc/quote
, 然後將更改推送到你自己的分支.
$ cd ../quote$ git commit -a -m 'my fork'[master 6151719] my fork 1 file changed, 1 insertion(+), 1 deletion(-)$ git tag v0.0.0-myfork$ git push https://github.com/you/quote v0.0.0-myforkTo https://github.com/you/quote * [new tag] v0.0.0-myfork -> v0.0.0-myfork$
然後你可以使用它作為替換:
$ cd ../hello$ echo 'replace "rsc.io/quote" v1.5.2 => "github.com/you/quote" v0.0.0-myfork' >>go.mod$ vgo list -mvgo: finding github.com/you/quote v0.0.0-myforkMODULE VERSIONgithub.com/you/hello -golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54rsc.io/quote v1.5.2 => github.com/you/quote v0.0.0-myforkrsc.io/sampler v1.3.1$ vgo buildvgo: downloading github.com/you/quote v0.0.0-myfork$ LANG=fr ./helloJe peux manger du verre, ça ne me fait pas mal.$
向後相容性
即使你想為你的項目使用 vgo
, 你也不可能要求你的所有的使用者都有 vgo
.
相反, 你可以建立一個 vendor
目錄, 以允許 go
命令使用者產生幾乎相同的構建(當然, 在 GOPATH
中編譯):
$ vgo vendor$ mkdir -p $GOPATH/src/github.com/you$ cp -a . $GOPATH/src/github.com/you/hello$ go build -o vhello github.com/you/hello$ LANG=es ./vhelloPuedo comer vidrio, no me hace daño.$
我說這些構建 "幾乎相同", 因為工具鏈看到的並在最終二進位檔案中記錄的匯入路徑是不同的. vendored
版本參見 vendor
目錄:
$ go tool nm hello | grep sampler.hello 1170908 B rsc.io/sampler.hello$ go tool nm vhello | grep sampler.hello 11718e8 B github.com/you/hello/vendor/rsc.io/sampler.hello$
除了這種差異, 構建應該產生相同的二進位檔案. 為了提供優雅的轉換, 基於 vgo
的構建完全忽略 vendor
目錄, 一如既往的模組感知 go
命令構建.
接下來 ?
請嘗試 vgo
. 在存放庫中開始標記(tagging)版本. 建立並檢入(check in) go.mod
檔案. 在 golang.org/issue
上上報問題, 並在標題開頭添加 "x/vgo:" 明天會有更多的博文. 謝謝, 玩得開心!