快速掌握 Go 工作區(qū)模式
大家好,我是煎魚(yú)。
在 Go 項(xiàng)目的模塊管理中,先是 GOPATH,然后到廢棄。再到強(qiáng)推 Go modules,從被社區(qū)抗拒到 rsc 硬上弓?,F(xiàn)在最新要了解的,就是工作區(qū)模式(workspace mode)。這是一個(gè)在 Go1.18 引入的重要特性。
之前一直沒(méi)提過(guò),今天補(bǔ)全這塊的知識(shí)點(diǎn)。
背景
在 Go1.11 起有了 Go modules 后,看起來(lái) Go 模塊管理逐步按序有了約束、規(guī)范了起來(lái)。但也帶來(lái)了一些使用上的問(wèn)題。
現(xiàn)實(shí)開(kāi)發(fā)時(shí),當(dāng)我們需要對(duì)多個(gè)關(guān)聯(lián)模塊進(jìn)行開(kāi)發(fā)(修改)時(shí),這個(gè)事情就麻煩了起來(lái)。我見(jiàn)過(guò)兩種方式。
1、第一種:直接在 go.mod 文件上配置 replace,配置到本地的開(kāi)發(fā)目錄。這是最常見(jiàn)的方式。
// go.mod
replace example.com/golang/text => "../eddycjy/golang/text"
這種做法經(jīng)常會(huì)有人不小心提交到 Git 倉(cāng)庫(kù)上。還挺折騰人的,一個(gè)不小心就為此 debug 了半天,或者發(fā)布部署一直卡著過(guò)不去。
2、第二種:直接在依賴模塊上編碼,編碼到一定的程度。才上傳 GitHub/GitLab。再去發(fā)布版本標(biāo)簽再引用。這種用法比較少,只有模塊比較簡(jiǎn)單且對(duì)程序比較自信的會(huì)這么干。(不推薦)
總的來(lái)講,就是有了 Go modules 后,多模塊間的依賴開(kāi)發(fā)還是挺麻煩的。要經(jīng)常 replace,有時(shí)候又會(huì)忘了刪。
go work 指令集
在大家痛苦了許久后,Go1.18 時(shí)終于發(fā)布了工作區(qū)模式的方式,來(lái)優(yōu)化這個(gè)用法和問(wèn)題。
以下是 go work 的指令集:
go work <command> [arguments]
- edit:從工具或腳本中編輯 go.work。
- init:初始化工作區(qū)文件(go.work)。
- sync:將工作區(qū)構(gòu)建列表同步到模塊。
- use:將模塊添加到工作區(qū)文件。
快速使用
接下來(lái)我們快速應(yīng)用 Go 工作區(qū)模式,讓大家有個(gè)直觀的了解。
需要注意,該特性需要確保 Go 版本 >= 1.18。
創(chuàng)建工作區(qū)
首先我們創(chuàng)建一個(gè)工作區(qū),執(zhí)行如下命令:
$ mkdir workspace-main && cd workspace-main
$ go work init
執(zhí)行完畢后會(huì)在該目錄下創(chuàng)建一個(gè) go.work 文件,文件內(nèi)容包含:
go 1.20
僅包含版本信息,因?yàn)楫?dāng)前是空白的工作區(qū),只有初始化行為。
創(chuàng)建演示模塊
$ mkdir hello-world && cd hello-world
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
寫(xiě)入代碼 hello.go:
package main
import (
"fmt"
"golang.org/x/example/hello/reverse"
)
func main() {
fmt.Println(reverse.String("Hello, 煎魚(yú)"))
}
如果你這時(shí)候直接 go run??赡軙?huì)出現(xiàn)如下報(bào)錯(cuò):
hello.go:6:5: no required module provides package golang.org/x/example/hello/reverse: go.mod file not found in current directory or any parent directory; see 'go help modules'
看著非常迷惑人,很多同學(xué)以為是環(huán)境變量 GO111MODULE 沒(méi)有設(shè)置為 on。其實(shí)是沒(méi)有將本模塊加入工作區(qū)中,導(dǎo)致運(yùn)行錯(cuò)誤。
所以可以看出來(lái),在設(shè)計(jì)上是先有項(xiàng)目,再有工作區(qū)的路徑。也是相對(duì)符合的。
這時(shí)候需要回到工作區(qū)目錄 workspace-main。執(zhí)行如下命令:
go work use ./hello-world
go.work 文件內(nèi)會(huì)變成:
$ cat go.work
go 1.20
use ./hello-world
再運(yùn)行程序:
$ go run hello-world/hello.go
魚(yú)煎 ,olleH
一切正常。
創(chuàng)建需修改的模塊
這時(shí)候我們有了一個(gè)實(shí)際的訴求,我們希望 golang.org/x/example/hello 改一下這個(gè) SDK 庫(kù)。
如果是以前的話,我們需要寫(xiě) replace 來(lái)解決。現(xiàn)在的話可以用工作區(qū)模式來(lái)完成這個(gè)訴求。
我們先需要回到工作區(qū)根目錄 workspace-main 下,拉取這個(gè) SDK 庫(kù)到工作區(qū)中:
git clone https://go.googlesource.com/example
再將其引入項(xiàng)目的工作區(qū)中:
go work use ./example/hello
go.work 文件會(huì)變成:
go 1.20
use (
./example/hello
./hello-world
)
這里需要注意,go work 以 go.mod 為單位。如果你直接引入 ./example。是無(wú)法對(duì) ./example/hello 的 module 起效果的。
在引入成功后,我們回到 ./example/hello 目錄下的 reverse.go 文件,新增一個(gè)用于 Demo 的方法:
...
func Hello() string {
return "煎魚(yú),你好!"
}
再到 hello 項(xiàng)目中,新增調(diào)用:
package main
import (
"fmt"
"golang.org/x/example/hello/reverse"
)
func main() {
fmt.Println(reverse.String("Hello, 煎魚(yú)"))
fmt.Println(reverse.Hello())
}
輸出結(jié)果:
魚(yú)煎 ,olleH
煎魚(yú),你好
一切正常。滿足不添加 replace 的要求,也使用了 go.work,不用擔(dān)心把 replace 不小心提交到 Git 倉(cāng)庫(kù)中。
另外 Go 工作區(qū)中的項(xiàng)目在進(jìn)行編譯時(shí),也是引用所配置好的工作區(qū)內(nèi)的模塊。而不是單單只針對(duì)開(kāi)發(fā)階段的 go run,也可以在產(chǎn)線上去使用,編譯成二進(jìn)制去應(yīng)用和部署。
場(chǎng)景匯總
我們已經(jīng)對(duì) Go 的工作區(qū)模式有了一定的了解,其使用場(chǎng)景聚焦在如下:
- 開(kāi)發(fā)較大的產(chǎn)品,其項(xiàng)目存在著多個(gè)互相依賴的模塊??梢灾苯釉O(shè)置成一個(gè)工作區(qū)。
- 開(kāi)發(fā)第三方庫(kù)(類(lèi)似 SDK 庫(kù)),需要對(duì)上游的模塊新增新特性。勢(shì)必要在本地模塊先引用做開(kāi)發(fā)、測(cè)試、驗(yàn)證。也可以直接使用工作區(qū)。
總結(jié)
今天我們快速了解了 Go 工作區(qū)模式(workspace mode)的背景、使用、場(chǎng)景。這對(duì)于解決項(xiàng)目中多模塊依賴有著一定的作用,可以不再需要去 go.mod 里 replace,算是給了一個(gè)規(guī)范化的解決方案。
但在實(shí)際應(yīng)用中,我們會(huì)發(fā)現(xiàn)工作區(qū)模式的便利度,其實(shí)不太高。可能依賴模塊數(shù)量少時(shí),還不如 replace 一把梭來(lái)得快。
另外目前階段的使用宣傳還是做得比較弱的,前兩天問(wèn)了一圈,還真有一些同學(xué)不知道,也沒(méi)有用過(guò)的。