版本化 Go 之旅

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

本文譯自 A Tour of Versioned Go (vgo), Go & Versioning 的第 2 部分, 著作權@歸原文所有.

對我而言, 設計意味著構建, 拆除和再次構建, 一遍又一遍.
為了編寫新的版本控制提案, 我構建了一個原型 vgo, 來處理許多細微的細節. 這篇博文展示了如何使用 vgo.

你現在可以通過運行 go get golang.org/x/vgo 下載並嘗試 vgo. Vgogo 命令的一個直接替換(和分支拷貝).
你運行 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.3v1.6.0, 該目錄中的構建仍將繼續使用 v1.5.2, 除非進行明確的升級(見下文).

go.mod 檔案列舉了依賴的最小集合, 忽略了已列舉中所隱含的.
在這種情況下, rsc.io/quote v1.5.2 依賴特定版本的 rsc.io/samplergolang.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/textrsc.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/quotersc.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/quotego.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:" 明天會有更多的博文. 謝謝, 玩得開心!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.