go mod 使用

goodspeed發表於2019-03-17

go modules 是 golang 1.11 新加的特性。現在1.12 已經發布了,是時候用起來了。Modules官方定義為:

模組是相關Go包的集合。modules是原始碼交換和版本控制的單元。 go命令直接支援使用modules,包括記錄和解析對其他模組的依賴性。modules替換舊的基於GOPATH的方法來指定在給定構建中使用哪些原始檔。

如何使用 Modules ?

  1. 把 golang 升級到 1.11(現在1.12 已經發布了,建議使用1.12)
  2. 設定 GO111MODULE

GO111MODULE

GO111MODULE 有三個值:off, onauto(預設值)

  • GO111MODULE=off,go命令列將不會支援module功能,尋找依賴包的方式將會沿用舊版本那種通過vendor目錄或者GOPATH模式來查詢。
  • GO111MODULE=on,go命令列會使用modules,而一點也不會去GOPATH目錄下查詢。
  • GO111MODULE=auto,預設值,go命令列將會根據當前目錄來決定是否啟用module功能。這種情況下可以分為兩種情形:
    • 當前目錄在GOPATH/src之外且該目錄包含go.mod檔案
    • 當前檔案在包含go.mod檔案的目錄下面。

當modules 功能啟用時,依賴包的存放位置變更為$GOPATH/pkg,允許同一個package多個版本並存,且多個專案可以共享快取的 module。

go mod

golang 提供了 go mod命令來管理包。

go mod 有以下命令:

命令 說明
download download modules to local cache(下載依賴包)
edit edit go.mod from tools or scripts(編輯go.mod
graph print module requirement graph (列印模組依賴圖)
init initialize new module in current directory(在當前目錄初始化mod)
tidy add missing and remove unused modules(拉取缺少的模組,移除不用的模組)
vendor make vendored copy of dependencies(將依賴複製到vendor下)
verify verify dependencies have expected content (驗證依賴是否正確)
why explain why packages or modules are needed(解釋為什麼需要依賴)

如何在專案中使用

示例一:建立一個新專案

  1. GOPATH 目錄之外新建一個目錄,並使用go mod init 初始化生成go.mod 檔案
➜  ~ mkdir hello
➜  ~ cd hello
➜  hello go mod init hello
go: creating new go.mod: module hello
➜  hello ls
go.mod
➜  hello cat go.mod
module hello

go 1.12
複製程式碼

go.mod檔案一旦建立後,它的內容將會被go toolchain全面掌控。go toolchain會在各類命令執行時,比如go get、go build、go mod等修改和維護go.mod檔案。

go.mod 提供了module, requirereplaceexclude 四個命令

  • module 語句指定包的名字(路徑)
  • require 語句指定的依賴項模組
  • replace 語句可以替換依賴項模組
  • exclude 語句可以忽略依賴項模組
  1. 新增依賴

新建一個 server.go 檔案,寫入以下程式碼:

package main

import (
	"net/http"
	
	"github.com/labstack/echo"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.Logger.Fatal(e.Start(":1323"))
}
複製程式碼

執行 go run server.go 執行程式碼會發現 go mod 會自動查詢依賴自動下載:

$ go run server.go
go: finding github.com/labstack/echo v3.3.10+incompatible
go: downloading github.com/labstack/echo v3.3.10+incompatible
go: extracting github.com/labstack/echo v3.3.10+incompatible
go: finding github.com/labstack/gommon/color latest
go: finding github.com/labstack/gommon/log latest
go: finding github.com/labstack/gommon v0.2.8
# 此處省略很多行
...

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1323
複製程式碼

現在檢視go.mod 內容:

$ cat go.mod

module hello

go 1.12

require (
	github.com/labstack/echo v3.3.10+incompatible // indirect
	github.com/labstack/gommon v0.2.8 // indirect
	github.com/mattn/go-colorable v0.1.1 // indirect
	github.com/mattn/go-isatty v0.0.7 // indirect
	github.com/valyala/fasttemplate v1.0.0 // indirect
	golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect
)
複製程式碼

go module 安裝 package 的原則是先拉最新的 release tag,若無tag則拉最新的commit,詳見 Modules官方介紹。 go 會自動生成一個 go.sum 檔案來記錄 dependency tree:

$ cat go.sum
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
... 省略很多行
複製程式碼
  1. 再次執行指令碼 go run server.go 發現跳過了檢查並安裝依賴的步驟。
  2. 可以使用命令 go list -m -u all 來檢查可以升級的package,使用go get -u need-upgrade-package 升級後會將新的依賴版本更新到go.mod * 也可以使用 go get -u 升級所有依賴

go get 升級

  • 執行 go get -u 將會升級到最新的次要版本或者修訂版本(x.y.z, z是修訂版本號, y是次要版本號)
  • 執行 go get -u=patch 將會升級到最新的修訂版本
  • 執行 go get package@version 將會升級到指定的版本號version
  • 執行go get如果有版本的更改,那麼go.mod檔案也會更改

示例二:改造現有專案(helloword)

專案目錄為:

$ tree
.
├── api
│   └── apis.go
└── server.go

1 directory, 2 files
複製程式碼

server.go 原始碼為:

package main

import (
    api "./api"  // 這裡使用的是相對路徑
    "github.com/labstack/echo"
)

func main() {
    e := echo.New()
    e.GET("/", api.HelloWorld)
    e.Logger.Fatal(e.Start(":1323"))
}
複製程式碼

api/apis.go 原始碼為:

package api

import (
    "net/http"

    "github.com/labstack/echo"
)

func HelloWorld(c echo.Context) error {
    return c.JSON(http.StatusOK, "hello world")
}
複製程式碼
  1. 使用 go mod init *** 初始化go.mod
$ go mod init helloworld
go: creating new go.mod: module helloworld
複製程式碼
  1. 執行 go run server.go
go: finding github.com/labstack/gommon/color latest
go: finding github.com/labstack/gommon/log latest
go: finding golang.org/x/crypto/acme/autocert latest
go: finding golang.org/x/crypto/acme latest
go: finding golang.org/x/crypto latest
build command-line-arguments: cannot find module for path _/home/gs/helloworld/api
複製程式碼

首先還是會查詢並下載安裝依賴,然後執行指令碼 server.go,這裡會丟擲一個錯誤:

build command-line-arguments: cannot find module for path _/home/gs/helloworld/api
複製程式碼

但是go.mod 已經更新:

$ cat go.mod
module helloworld

go 1.12

require (
        github.com/labstack/echo v3.3.10+incompatible // indirect
        github.com/labstack/gommon v0.2.8 // indirect
        github.com/mattn/go-colorable v0.1.1 // indirect
        github.com/mattn/go-isatty v0.0.7 // indirect
        github.com/valyala/fasttemplate v1.0.0 // indirect
        golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect
)
複製程式碼
那為什麼會丟擲這個錯誤呢?

這是因為 server.go 中使用 internal package 的方法跟以前已經不同了,由於 go.mod會掃描同工作目錄下所有 package 並且變更引入方法,必須將 helloworld當成路徑的字首,也就是需要寫成 import helloworld/api,以往 GOPATH/dep 模式允許的 import ./api 已經失效,詳情可以檢視這個 issue

  1. 更新舊的package import 方式

所以server.go 需要改寫成:

package main

import (
    api "helloworld/api"  // 這是更新後的引入方法
    "github.com/labstack/echo"
)

func main() {
    e := echo.New()
    e.GET("/", api.HelloWorld)
    e.Logger.Fatal(e.Start(":1323"))
}
複製程式碼

一個小坑:開始在golang1.11 下使用go mod 遇到過 go build github.com/valyala/fasttemplate: module requires go 1.12 這種錯誤,遇到類似這種需要升級到1.12 的問題,直接升級golang1.12 就好了。幸虧是在1.12 釋出後才嘗試的go mod ?‍♂️

  1. 到這裡就和新建立一個專案沒什麼區別了

使用replace替換無法直接獲取的package

由於某些已知的原因,並不是所有的package都能成功下載,比如:golang.org下的包。

modules 可以通過在 go.mod 檔案中使用 replace 指令替換成github上對應的庫,比如:

replace (
	golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
複製程式碼

或者

replace golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
複製程式碼

參考連結

References

[1] Modules官方介紹: github.com/golang/go/w… [2] issue: github.com/golang/go/i… [3] 這種錯誤: github.com/golang/go/i… [4] Modules官方介紹: github.com/golang/go/w… [5] Golang 1.11 新功能介紹 – Modules: www.lightblue.asia/golang-1-11… [6] What are Go modules and how do I use them?: talks.godoc.org/github.com/… [7] go mod doesn't work for github.com/gomarkdown/markdown/html : github.com/golang/go/i… [8] 再探go modules:使用與細節: www.cnblogs.com/apocelipes/… [9] 初窺Go module: tonybai.com/2018/07/15/…


最後,感謝女朋友支援和包容,比❤️

也可以在公號輸入以下關鍵字獲取歷史文章:公號&小程式 | 設計模式 | 併發&協程

掃碼關注

相關文章