背景
Go 1.18除了引入泛型(generics)、模糊測試(Fuzzing)之外,另外一個重大功能是引入了工作區模式(workspace mode)。
工作區模式對本地同時開發多個有依賴的Module的場景非常有用。
舉個例子,我們現在有2個Go module專案處於開發階段,其中一個是example.com/main
,另外一個是example.com/util
。其中example.com/main
這個module需要使用example.com/util
這個module裡的函式。
我們來看看Go 1.18版本前後如何處理這種場景。
Go 1.18之前怎麼做
在Go 1.18之前,對於上述場景有2個處理方案:
方案1:被依賴的模組及時提交程式碼到程式碼倉庫
這個方案很好理解,既然example.com/main
這個module依賴了example.com/util
這個module,那為了example.com/main
能使用到example.com/util
的最新程式碼,需要做2個事情
- 本地開發過程中,如果
example.com/util
有修改,都馬上提交程式碼到程式碼倉庫,然後打tag - 緊接著
example.com/main
更新依賴的example.com/util
的版本號(tag),可以使用go get -u
命令。
這種方案比較繁瑣,每次example.com/util
有修改,都要提交程式碼,否則example.com/main
這個module就無法使用到最新的example.com/util
。
方案2:go.mod裡使用replace指令
為了解決方案1的痛點,於是有了方案2:在go.mod裡使用replace指令。
通過replace指令,我們可以直接使用example.com/util
這個module的本地最新程式碼,而不用把example.com/util
的程式碼提交到程式碼倉庫。
為了方便大家理解,我們直接上程式碼。程式碼目錄結構如下:
module
|
|------main
| |---main.go
| |---go.mod
|------util
| |---util.go
| |---go.mod
main目錄下的main.go
程式碼如下:
//main.go
package main
import (
"fmt"
"example.com/util"
)
func main() {
result := util.Add(1, 2)
fmt.Println(result)
}
main目錄下的go.mod
內容如下:
module example.com/main
go 1.16
require example.com/util v1.0.0
replace example.com/util => ../util
util目錄下的util.go
程式碼如下:
// util.go
package util
func Add(a int, b int) int {
return a + b
}
util目錄下的go.mod
內容如下:
module example.com/util
go 1.16
這裡最核心的是example.com/main
這個module的go.mod
,最後一行使用了replace指令。
module example.com/main
go 1.16
require example.com/util v1.0.0
replace example.com/util => ../util
通過replace指令,使用go命令編譯程式碼的時候,會找到本地的util目錄,這樣example.com/main
就可以使用到本地最新的example.com/util
程式碼。進入main目錄,執行程式碼,結果如下所示:
$ cd main
$ go run main.go
3
但是這種方案也有個問題,我們在提交example.com/main
這個module的程式碼到程式碼倉庫時,需要刪除最後的replace指令,否則其他開發者下載後會編譯報錯,因為他們本地可能沒有util目錄,或者util目錄的路徑和你的不一樣。
程式碼開源地址:Go 1.18之前使用replace指令
Go 1.18工作區模式
為了解決方案2的痛點,在Go 1.18裡新增了工作區模式(workspace mode)。
該模式下不再需要在go.mod
裡使用replace指令,而是新增一個go.work
檔案。
話不多說,直接上程式碼。程式碼目錄結構如下:
workspace
|------go.work
|
|------main
| |---main.go
| |---go.mod
|------util
| |---util.go
| |---go.mod
main目錄下的main.go
程式碼如下:
//main.go
package main
import (
"fmt"
"example.com/util"
)
func main() {
result := util.Add(1, 2)
fmt.Println(result)
}
main目錄下的go.mod
內容如下:沒有了方案2裡最後一行的replace指令
module example.com/main
go 1.16
require example.com/util v1.0.0
util目錄下的util.go
程式碼如下:
// util.go
package util
func Add(a int, b int) int {
return a + b
}
util目錄下的go.mod
內容如下:
module example.com/util
go 1.16
go.work
內容如下:
go 1.18
use (
./main
./util
)
在workspace目錄下執行如下命令即可自動生成go.work
$ go1.18beta1 work init main util
go1.18beta1 work init
後面跟的main
和util
都是Module對應的目錄。
如果go命令執行的當前目錄或者父目錄有go.work
檔案,或者通過GOWORK
環境變數指定了go.work
的路徑,那go命令就會進入工作區模式。在工作區模式下,go就可以通過go.work
下的module路徑找到並使用本地的module程式碼。
在main目錄或者workspace目錄,都可以執行main.go
,結果如下所示:
$ go1.18beta1 run main/main.go
3
$ cd main/
$ go1.18beta1 run main.go
3
這種模式下,我們對example.com/main
沒有任何本地侵入性修改,不用像方案2那樣,提交程式碼前還需要更新go.mod
檔案。example.com/main
裡的內容都可以直接提交到程式碼倉庫。
程式碼開源地址:Go 1.18工作區模式
注意:go.work
不需要提交到程式碼倉庫中,僅限於本地開發使用。
總結
為了解決多個有依賴的Module本地同時開發的問題,Go 1.18引入了工作區模式。
工作區模式是對已有的Go Module開發模式的優化,關於工作區模式的更多細節可以參考本文最後的References。
開源地址
文章和示例程式碼開源在GitHub: Go語言初級、中級和高階教程。
公眾號:coding進階。關注公眾號可以獲取最新Go面試題和技術棧。
個人網站:Jincheng's Blog。
知乎:無忌。
References
- Proposal提案:https://go.googlesource.com/p...
- workspace官方教程:https://go.dev/doc/tutorial/w...
- workspace語法:https://go.dev/ref/mod#go-wor...
- go work命令手冊:https://pkg.go.dev/cmd/go@mas...
- go 1.18 release notes: https://tip.golang.org/doc/go...
- Go如何引用本地Module:https://github.com/jincheng9/...
- polarisxu: https://polarisxu.studygolang...