快速掌握 Go 工作區模式

發表於2023-09-27

大家好,我是煎魚。

在 Go 專案的模組管理中,先是 GOPATH,然後到廢棄。再到強推 Go modules,從被社群抗拒到 rsc 硬上弓。現在最新要了解的,就是工作區模式(workspace mode)。這是一個在 Go1.18 引入的重要特性。

之前一直沒提過,今天補全這塊的知識點。

背景

在 Go1.11 起有了 Go modules 後,看起來 Go 模組管理逐步按序有了約束、規範了起來。但也帶來了一些使用上的問題。

現實開發時,當我們需要對多個關聯模組進行開發(修改)時,這個事情就麻煩了起來。我見過兩種方式。

1、第一種:直接在 go.mod 檔案上配置 replace,配置到本地的開發目錄。這是最常見的方式。

// go.mod

replace example.com/golang/text => "../eddycjy/golang/text"

這種做法經常會有人不小心提交到 Git 倉庫上。還挺折騰人的,一個不小心就為此 debug 了半天,或者釋出部署一直卡著過不去。

2、第二種:直接在依賴模組上編碼,編碼到一定的程度。才上傳 GitHub/GitLab。再去釋出版本標籤再引用。這種用法比較少,只有模組比較簡單且對程式比較自信的會這麼幹。(不推薦)

總的來講,就是有了 Go modules 後,多模組間的依賴開發還是挺麻煩的。要經常 replace,有時候又會忘了刪。

go work 指令集

在大家痛苦了許久後,Go1.18 時終於釋出了工作區模式的方式,來最佳化這個用法和問題。

以下是 go work 的指令集:

go work <command> [arguments]
  • edit:從工具或指令碼中編輯 go.work。
  • init:初始化工作區檔案(go.work)。
  • sync:將工作區構建列表同步到模組。
  • use:將模組新增到工作區檔案。

快速使用

接下來我們快速應用 Go 工作區模式,讓大家有個直觀的瞭解。需要注意,該特性需要確保 Go 版本 >= 1.18。

建立工作區

首先我們建立一個工作區,執行如下命令:

$ mkdir workspace-main && cd workspace-main 
$ go work init

執行完畢後會在該目錄下建立一個 go.work 檔案,檔案內容包含:

go 1.20

僅包含版本資訊,因為當前是空白的工作區,只有初始化行為。

建立演示模組

$ mkdir hello-world && cd hello-world
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello

寫入程式碼 hello.go:

package main

import (
    "fmt"

    "golang.org/x/example/hello/reverse"
)

func main() {
    fmt.Println(reverse.String("Hello, 煎魚"))
}

如果你這時候直接 go run。可能會出現如下報錯:

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'

看著非常迷惑人,很多同學以為是環境變數 GO111MODULE 沒有設定為 on。其實是沒有將本專案加入工作區中,導致執行錯誤。

所以可以看出來,在設計上是先有專案,再有工作區的路徑。也是相對符合的。

這時候需要回到工作區目錄 workspace-main。執行如下命令:

go work use ./hello-world

go.work 檔案內會變成:

$ cat go.work 
go 1.20

use ./hello-world

再執行程式:

$ go run hello-world/hello.go 
魚煎 ,olleH

一切正常。

建立需修改的模組

這時候我們有了一個實際的訴求,我們希望 golang.org/x/example/hello 改一下這個 SDK 庫。

如果是以前的話,我們需要寫 replace 來解決。現在的話可以用工作區模式來完成這個訴求。

我們先需要回到工作區根目錄 workspace-main 下,拉取這個 SDK 庫到工作區中:

git clone https://go.googlesource.com/example

再將其引入專案的工作區中:

go work use ./example/hello

go.work 檔案會變成:

go 1.20

use (
    ./example/hello
    ./hello-world
)

這裡需要注意,go work 以 go.mod 為單位。如果你直接引入 ./example。是無法對 ./example/hello 的 module 起效果的。

在引入成功後,我們回到 ./example/hello 目錄下的 reverse.go 檔案,新增一個用於 Demo 的方法:

...
func Hello() string {
    return "煎魚,你好!"
}

再到 hello 專案中,新增呼叫:

package main

import (
    "fmt"

    "golang.org/x/example/hello/reverse"
)

func main() {
    fmt.Println(reverse.String("Hello, 煎魚"))
    fmt.Println(reverse.Hello())
}

輸出結果:

魚煎 ,olleH
煎魚,你好

一切正常。滿足不新增 replace 的要求,也使用了 go.work,不用擔心把 replace 不小心提交到 Git 倉庫中。

另外 Go 工作區中的專案在進行編譯時,也是引用所配置好的工作區內的模組。而不是單單隻針對開發階段的 go run,也可以在產線上去使用,編譯成二進位制去應用和部署。

場景彙總

我們已經對 Go 的工作區模式有了一定的瞭解,其使用場景聚焦在如下:

  • 開發較大的產品,其專案存在著多個互相依賴的模組。可以直接設定成一個工作區。
  • 開發第三方庫(類似 SDK 庫),需要對上游的模組新增新特性。勢必要在本地模組先引用做開發、測試、驗證。也可以直接使用工作區。

總結

今天我們快速瞭解了 Go 工作區模式(workspace mode)的背景、使用、場景。這對於解決專案中多模組依賴有著一定的作用,可以不再需要去 go.mod 裡 replace,算是給了一個規範化的解決方案。

但在實際應用中,我們會發現工作區模式的便利度,其實不太高。可能依賴模組數量少時,還不如 replace 一把梭來得快。

另外目前階段的使用宣傳還是做得比較弱的,前兩天問了一圈,還真有一些同學不知道,也沒有用過的。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

Go 圖書系列

推薦閱讀

相關文章