go module


前言

go 1.5 引進了vendor管理工程依賴包,但是vendor的存放路徑是在GOPATH底下,另外每個依賴還可以有自己的vendor,通常會弄得很亂,盡管dep管理工具可以將vendor平級化管理,但是相對GOPATH的路徑是逃不掉的。另外,各個包的版本管理也顯得原始,甚至有的開發將依賴包從github直接download下來自己放到GOPATH底下的vendor。go的依賴包管理一致是開發者詬病的一個痛點。所以在千呼萬喚中,go 1.11 終於引進了go module管理工程的包依賴,去除了項目包管理對GOPATH的依賴,明確了依賴包的版本管理。

 

定義

一個module是go相關包版本信息的收集單元。記錄了精准的必須依賴信息和重新編譯依賴。

 

從示例開始

go module的使用其實十分容易上手,下面我會以一個例子來說明。

示例的go環境信息:

$ go version

go version go1.12.4 darwin/amd64

 

下面這個例子是依賴github.com/sirupsen/logrus 輸出一行日志。在GOPATH外創建一個mytest的目錄,然后創建一個main.go的文件,內容如下:

 

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
        // Add this line for logging filename and line number!
    log.SetReportCaller(true)

    log.Println("hello world")
}

執行

go mod init mytest

其實mytest是我指定的module名稱,可以是任意的命名,但是一定要指定,否則會報錯 go: cannot determine module path for source directory。

然后執行go build就會成功編譯,並且多了go.mod和go.sum兩個module相關的文件:

$ ls
go.mod    go.sum    main.go    mytest

$ cat go.mod 
module mytest

go 1.12

require github.com/sirupsen/logrus v1.4.2

$ cat go.sum 
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

從示例中可以看出go.mod文件存放的是工程包依賴信息,而go.sum里面存放的是依賴包的校驗信息。主要關注go.mod的信息。可以看到,如果我們不指定依賴包的版本信息,go build默認是會替我們去拉取該依賴包的最新版本。

所以可以總結,go module的使用分為以下幾步:

  • go mod init $moduleName 初始化module信息。
  • go build或者go test等標准命令自動更新工程的依賴包信息。
  • 如果有需要可以使用go get  $packageName@$version,例如go get foo@v1.2.3, go get foo@master, go get foo@e3702bed2,也可以直接修改go.mod或者使用go mod edit(文章后面會講到)獲取特定的依賴包版本。

以上就是基本的go module工作流程,已經可以滿足日常的工作流程要求,下面會詳細的講解go module的其他用法。

 

詳細用法

那么go module一共有多少種玩法呢?直接運行go mod就會有答案:

$ go mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

    go mod <command> [arguments]

The commands are:

    download    download modules to local cache
    edit        edit go.mod from tools or scripts
    graph       print module requirement graph
    init        initialize new module in current directory
    tidy        add missing and remove unused modules
    vendor      make vendored copy of dependencies
    verify      verify dependencies have expected content
    why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

其中init前面我已經講過了,這里就不再重復。

 

download 

下載依賴包到緩存目錄。

 

edit

提供命令版本編輯go.mod的功能,例如go mod edit -fmt go.mod 會格式化go.mod。

用法 go mod edit [flag] [go.mod]

其中flag選項有:

  • -fmt 格式化go.mod文件
  • -require=$package:@version添加依賴,會覆蓋已存在的相同依賴。添加依賴更推薦使用go get,因為go get會更新相關的go.mod文件,而edit只會更新你指定的go.mod文件。
  • -droprequire=$package:@version 移除依賴
  • -replace=$oldPackage=$newPackage 更新已經存在的依賴。通常用於私有倉庫代碼覆蓋共有倉庫。  

這里我重點說下-replace 選項,因為在生產中經常遇到的一種情況是由於這樣那樣的原因我們需要fork一個私有倉庫去改動第三方開源庫,例如有個小哥針對logrus做了二次開發github.com/gogap/logrus,這個時候就需要用github.com/gogap/logrus替換之前的第三方開源庫github.com/sirupsen/logrus,操作如下:

$ go mod edit -replace="github.com/sirupsen/logrus=github.com/gogap/logrus@v0.8.2"
$ go build
go: finding github.com/gogap/logrus v0.8.2
go: downloading github.com/gogap/logrus v0.8.2
go: extracting github.com/gogap/logrus v0.8.2

$ cat go.mod 
module mytest

go 1.12

require github.com/sirupsen/logrus v1.4.2

replace github.com/sirupsen/logrus => github.com/gogap/logrus v0.8.2

 

graph

顯示依賴關系(圖)。

$ go mod graph 
mytest github.com/sirupsen/logrus@v1.4.2
github.com/sirupsen/logrus@v1.4.2 github.com/davecgh/go-spew@v1.1.1
github.com/sirupsen/logrus@v1.4.2 github.com/konsorten/go-windows-terminal-sequences@v1.0.1
github.com/sirupsen/logrus@v1.4.2 github.com/pmezard/go-difflib@v1.0.0
github.com/sirupsen/logrus@v1.4.2 github.com/stretchr/objx@v0.1.1
github.com/sirupsen/logrus@v1.4.2 github.com/stretchr/testify@v1.2.2
github.com/sirupsen/logrus@v1.4.2 golang.org/x/sys@v0.0.0-20190422165155-953cdadca894

 

tidy

增加缺失的包並且移除沒有依賴的包。自動去下載依賴包,並且緩存到$GOPATH/pkg/mod目錄下。

需要注意的是,tidy會自動更新依賴包的版本,所以如果不是初建的項目還是盡量少用tidy,盡量用go get精准控制新增的依賴包。

vendor

把依賴包拷貝到vendor目錄底下。前面說了那么多想必你一定有一個疑問:go build的時候需要現場去拉取依賴包,如果我的編譯機沒有外網(訪問不了github)怎么辦?vendor就是為了應用這種情況,在本地開發機(有外網)執行 go mod vendor 將依賴包拷貝到vendor底下,然后將代碼push到編譯機 執行 go build -mod=vendor。示例:

$ go mod vendor
$ ls
go.mod    go.sum    main.go    mytest    vendor
$ go build -mod=vendor

 

verify

校驗依賴關系

$ go mod verify
all modules verified

 

why

指出為什么需要依賴包。與graph的區別是,why只能解釋某一個特定的依賴包,而graph則是給出完整的依賴關系圖。

$ go mod why github.com/konsorten/go-windows-terminal-sequences
# github.com/konsorten/go-windows-terminal-sequences
mytest
github.com/sirupsen/logrus
github.com/konsorten/go-windows-terminal-sequences

 

同工程下的依賴管理

例如建立一個webserver的工程,目錄為/Users/saas/src/awesomeProject/webserver,GOPATH設置為/Users/saas, webserver下的目錄結構為:

$ tree
.
├── go.mod
├── google
│   └── google.go
├── helloworld
├── server.go
└── userip
    └── userip.go

2 directories, 5 files

其中google目錄為packge google,userip目錄為package userip,那么我們要在server.go如何引用google和userip這兩個包呢?只需:

import (
    "helloworld/google"
    "helloworld/userip"
)

helloworld 是go mod init helloworld時指定的module名稱,所以helloworld索引到了webserver目錄,helloworld/google指的是webserver底下的google包。如果不指定module的名稱,默認是GOPATH下的路徑,即為awesomeProject/webserver,引用google包時就需要指定awesomeProject/webserver/google。如果GOPATH沒有指定,又沒有指定module的名字則報錯:

$ export GOPATH=""
$ go mod init
go: cannot determine module path for source directory /Users/saas/src/awesomeProject/webserver (outside GOPATH, no import comments)

指定module就可以了,即便沒有GOPATH:

$ go mod init helloworld
go: creating new go.mod: module helloworld

go build時默認會用module的名字(base name)給程序名稱,這里是helloworld。如果module名稱為 awesomeProject/webserver則是webserver。

 

Goland IDE打開Go Module

上面例子中發現在Goland IDE中helloworld/google會被標紅,說找不到helloworld這個目錄,說明IDE的Go Module功能還沒有打開,需要如下設置:

 

總結

文章通過一個打印日志的例子演示了所有go module的用法,其中包括日常基本用法和全面的用法介紹。新增依賴包的更新推薦使用go get。依賴包的替換推薦使用go mod edit -replace。在編譯機網絡有限制的時候提供了vendor的解決方案。

 

參考

https://github.com/golang/go/wiki/Modules

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM