Go Modules

1 GOPATH vs Go Modules

Go1.5GOPATHGOROOTGOROOTGoGo1.5Go1.7Vendor$GOPATH/src/vendorvendorgodepgovendorgolide
Go1.11GOPATH
go getgo installgo build
Go1.11import
GOROOT/srcGOPATH/src./vendor
GOPATH$HOME/goGOPATH存放
Go1.11GOPATHGoGOPATH/src$GOPATH/srcGo1.11GOGo Modules
go mod vendor-mod=vendorvgovendor

2 Go Modules、Go Module Proxy 和 http://goproxy.cn

Go ModulesGo 1.11vgoGo 1.12Go 1.13
Go Module ProxyGo ModulesGo
proxy.golang.orgGogoproxy.cnGo1.13Go
# 开启 GO Modules 包管理方式
$ go env -w GO111MODULE=on
# 设置代理为 https://goproxy.cn
# 你也可以设置多个代理,通过逗号分隔开,模块从左至右设置的代理中查找获取
$ go env -w GOPROXY=https://goproxy.cn,direct
“ 注意:模块可能是一个项目,项目下面可以包含很多包。

3 Go Modules 相关知识

3.1 语义化版本控制规范

Go Moduleshttps://semver.org/lang/zh-CN/
TagTagCommitv0.0.1-20180523231146-b3f5c0f6e5f1v


Go Modulesimportpathv1.0.0v2.0.0github.com/xx/xxgithub.com/xx/xx/v2go get

3.2 go.mod

go.modGo ModulesGo ModulesGO111MODULE=off
module my/thing
go 1.12
require other/thing v1.0.2
require new/thing/v2 v2.3.4

// 注释:也可以用块结构设定多个依赖模块
require (
	new/thing v2.3.4
	old/thing v1.2.3
	github.com/my/repo v0.0.1-20180523231146-b3f5c0f6e5f1
)
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5

其中:

  • module, 定义模块的路径(名称)
  • go, 设置期望的Go版本
  • require, 在给定的版本或者更高的版本模块中,指定依赖一个特定版本
  • exclude, 排除特定模块版本依赖
  • replace, 将指定模块版本替换为其他模块版本
requirereplacego.modgo.mod// indirectgo buildgo getgo installgo listgo testgo mod tidygo mod whygo.mod
replace=>

3.3 go.sum

go.sumgo.modgo.sumgo.sum
go.sumgo.modgo.modhgo.sum
<模块路径> <版本>[/go.mod] <校验和>
// 示例:
// cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
// github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
// golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
go.sumgo.sum

3.4 GOPROXY、GONOPROXY、GOSUMDB、GONOSUMDB、GOPRIVATE

GOPROXYGOPROXYhttps://proxy.golang.org,directGOPROXY"direct"GOPROXY"off",|
GOSUMDB
GOSUMDB="sum.golang.org"                                        # 默认配置,URL 默认都是 https://,后跟数据库地址
GOSUNDB="sum.golang.google.cn"                                  # 中国大陆可访问
GOSUMDB="sum.golang.org+<publickey>"                            # 使用除了sum.golang.org 和 sum.golang.google.cn 域名外其他需要给出公钥 
GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"     # 
GOSUMDB="off"                                                   # 关闭校验,任何模块可以被使用
proxy.golang.orgsum.golang.org
# 任何匹配*.corp.example.com为前缀的模块都被视为私有模块,包括如git.crop.example.com/xxx, rsc.io/private/yyy
# 配置GORPIVATE过滤规则时,通过逗号分隔配置多个匹配路径
$ go env -w GOPRIVATE=*.corp.example.com,rsc.io/private
GONOPROXYGONOSUMDBGOPRIVATEGOPRIVATEGONOPROXYnoneGOPROXYGOPRIVATE
GOPRIVATE=*.corp.example.com
GOPROXY=proxy.example.com
GONOPROXY=none
GOPROXYGONOPROXY
GOPROXY=https://proxy.golang.org
GONOPROXY=gitlab.com/xxx
GONOSUMDB=$GONOPROXY
GOPROXYGONOPROXY=*GOPROXY=off
*

4 Go Modules 及其相关常用命令

go get
# 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号,  y是次要版本号)
$ go get -u [URL]
# 将会升级到最新的次要版本
$ go get -u=patch [URL]
# 将不会校验校验码,同 GOSUMDB=off 效果一致;另外可以下载来自非https域名的模块
$ go get -insecure [URL]
# 下载指定版本的模块,如最新版本是v2.2.0,将级下载v2.1.0
$ go get github.com/urfave/cli/v2@v2.1.0
# 拉取master分支最新提交
$ go get github.com/my/repo@master
# 拉取某个指定的提交
$ go get github.com/my/repo@772611b
go list -m allgo list -m -u allgo get -u ./...go get -u=patch ./...go build ./...go test ./...go clean -modcache$HOME/go/mod
go getgo.mod

go mod 相关:

$ go mod download    下载依赖的module到本地cache
$ go mod edit        编辑go.mod文件
$ go mod graph       打印模块依赖图
$ go mod init        在当前文件夹下初始化一个新的module, 创建go.mod文件
$ go mod tidy        增加丢失的module,去掉未用的module
$ go mod vendor      将依赖复制到vendor目录下
$ go mod verify      校验依赖
$ go mod why         解释为什么需要依赖

具体使用:

$ go mod download [-x] [-json] [modules]$HOME/go/pkg/mod/cache-x-json$ go mod verify$ go mod edit [editing flags] [go.mod]-fmtgo.mod-module=new-module-pathmodule-require=path@version-droprequire=pathrequire()go getgo.mod-exclude=path@version-dropexclude=path@versionexclude-replace=old[@v]=new[@v]-go=version-print-jsongo list -m -json all$ go mod graph$ go mod init [module][moudle]go.mod$ go mod tidy [-v]-v$ go mod vendor [-v]-v$ go mod why [-m] [-vendor] packagesgo mod graphgo mod graphgo mod why

5 Go Modules 实践

5.1 创建一个 Go Modules 项目

go.modGOPATH
Go1.11Go 1.13$ cd GO111MODULE$ go mod init [your module path]$ go mod init github.com/my/repo$ go mod init helloworld
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}
$ go build -o hello.go$ ./hello$ cat go.mod
module githu.com/my/repo

go 1.14

require rsc.io/quote v1.5.2

5.2 本地包依赖管理

Go ModulesGOPATHGOPATH$GOPATH/src
- {your project path}
    - bin
    - pkg
    - src
        - {package1 name}        # 包名文件夹必须与包名一致
            - package files
        - {package2 name}
            - package files
        - main
            - main.go
Go Modules
Go Modulesrsc.io/quoteGo Modules
.
├── bin
├── cmd
│   └── hello
│       └── hello.go
├── go.mod
├── go.sum
├── pkg1
│   ├── pkg1_src.go
│   └── pkg1_test.go
└── pkg2
    └── pkg2_src.go

其中 hello.go 、pkg1_src.go 、pkg1_test.go和 pkg2_src.go 内容分别为:

package main

import (
    "fmt"
    "rsc.io/quote"
    "github.com/my/repo/pkg1"
)

func main() {
    fmt.Println(quote.Hello())
    pkg1.HelloPkg1()
}

package pkg1                                                                    
                                                                                
import (                                                                        
    "fmt"                                                                       
)                                                                               
                                                                                
func HelloPkg1() string {                                                                                                        
    fmt.Println("Hello pkg1")                                                   
    return "Hello pkg1"                                                         
}

package pkg1                                                                                                                     
                                                                                
import "testing"                                                                
                                                                                
func TestHello(t *testing.T) {                                                  
    want := "Hello pkg1"                                                        
    if got := HelloPkg1(); got != want {                                        
        t.Errorf("Hello() = %q, want %q", got, want)                            
    }                                                                           
}     

package pkg2

import (
        "fmt"
)

func HelloPkg2() {
        fmt.Println("Hello pgk2")
}
hello.gobin
# 在项目根目录中编译,./... 模式表示匹配在当前模块中所有的packages
# 注意:采用 ./... -o 只指定目录,不能指定具体的生成对象名称,因为你可能有多个可执行文件一起生成
$ go build -o bin ./...

# 也可以单独编译我们的可执行文件,并指定生成名称
$ go build -o bin/hello_rename cmd/hello/hello.go
binhello./bin/hello
Ahoy, world!
Hello pgk1

你也可以单独将某个包编译成 Go 静态库:

# 单独编译某个包,同样的要找到这个包也需要使用项目模块路径 + 具体包路径
$ go build -buildmode=archive -o bin/libpkg1.a github.com/my/repo/pkg1

5.3 如何发布我们的模块呢?

Go
# 【step1】在发布之前,建议执行 tidy,清除掉无关或者我们使用到但尚未添加进来的模块
$ go mod tidy
# 【step2】测试本项目模块中所有测试样例,确保测试成功,go test all 会测试依赖在内的所有测试样例
$ go test ./...

# 确保 go.sum 和 go.mod 文件都一起提交到该版本中,go.sum 不是类似 nodejs 的 package-local.json 锁文件,更多地它可以帮助校验本地下载地模块是否被篡改
# 【step3】版本提交
# git 提交操作,发布v1.0.0版本
$ git add -A
$ git commit -m "hello: changes for v1.0.0"
$ git tag v1.0.0
$ git push origin v1.0.0

在 Go 版本发布中,模块导入路径默认是省略了 v0、v1 主版本的。至于为什么这样设计,可以参考: https://github.com/golang/go/issues/24301 。

如果要发布 v2 或者更高的版本?在官方的 FAQ 中很详细的介绍了操作和一些建议,比如你有一个版本仓库,已经打上了 v2.0.0 的标记,但是你还没有采用 Go Modules 方式,建议你后续直接打上 v3,从而很清晰的而区分采用了 Go Modules 方式的版本。下面以发布一个 v2+ 版本其中一种方式(另外参见 https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher )作为示例:

# 【step1】 将你的模块路径带上v2+信息,如
$ go mod edit -module github.com/my/repo/v2
# 【step2】 更新你项目中使用了其他本地包的模块路径,都加上v2,如我们上面的hello.go,则变为github.com/my/repo/v2/pkg1
# 【step3】 版本控制发布 v2.x.x tag

5.4 迁移到 Go Modules 包管理

很多 Go 项目使用以前的老的包管理方式,Go 在迁移方面也做了很多工作,包括从以前的依赖管理自动迁移到 Go Modules 方式以及诸多迁移注意事项。这里就不展开了,具体参见 https://github.com/golang/go/wiki/Modules#migrating-to-modules 。

当然,最简单的迁移方式就是使用 Go1.13 或以上版本,重新组织你的项目和依赖,以及所有的导入包路径的修改,这相当于新初始化一个 Go Modules 项目。

6 总结

GOPATHVendorvgoGo ModulesGoGo ModulesGopher

7 参考资料

golang.google.cngolang.org