[TOC] # 简介 `GO111MODULE`的取值为 off, on, or auto (默认值)。 * off: GOPATH mode,查找vendor和GOPATH目录 * on:module-aware mode,使用 go module,忽略GOPATH目录 * auto:如果当前目录不在$GOPATH**并且**当前目录(或者父目录)下有go.mod文件,则使用`GO111MODULE`, 否则仍旧使用 GOPATH mode。 go.mod文件可以通过require,replace和exclude语句使用的精确软件包集。 * require语句指定的依赖项模块 * replace语句可以替换依赖项模块 * exclude语句可以忽略依赖项模块 注意,go modules 下载的包在`GOPATH/pkg/mod` 执行 go clean -modcache 清除缓存 ---- * govendor * dep * glide * godep 这些包管理工具都是基于`GOPATH`或者`vendor`目录,并不能很好的解决不同版本依赖问题。Modules是在`GOPATH`之外一套新的包管理方式 首先要把go升级到1.11。 升级后,可以设置通过一个环境变量`GO111MODULE`来激活modules: * GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。 * GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。 * GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:当前目录在GOPATH/src之外且该目录包含go.mod文件,或者当前文件在包含go.mod文件的目录下面。 当module功能启用时,`GOPATH`在项目构建过程中不再担当import的角色,但它仍然存储下载的依赖包,具体位置在`$GOPATH/pkg/mod`。 自从`Go`官方从去年推出 1.11 之后,增加新的依赖管理模块并且更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。 从 Go 1.11 开始,Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中,为了兼容性,Go 命令仍然在旧的 GOPATH 模式下运行。从 Go 1.13 开始,模块模式将成为默认模式。 本文将介绍使用模块开发 Go 代码时出现的一系列常见操作: * 创建一个新模块。 * 添加依赖项。 * 升级依赖项。 * 删除未使用的依赖项。 下面使用的案例都是以`GIN`模块为例。 在这之前呢,需要先设置一些环境变量: ~~~go export GO111MODULE=on export GOPROXY=https://goproxy.io // 设置代理 ~~~ # 创建一个新模块 go mod会下载到`pkg/mod/cache` 你可以在 $GOPATH/src 之外的任何地方创建一个新的目录。比如: > mkdir backend && cd backend 然后初始化`go mod init backend`,成功之后你会发现目录下会生成一个`go.mod`文件. > $ cat go.mod > > 内容如下 > > module backend > > go 1.12 # 添加依赖项 创建一个文件 main.go 然后加入以下代码,这里直接 import 了 gin 的依赖包。 > vim main.go ~~~go package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 } ~~~ go build 之后,会在 go.mod 引入所需要的依赖包。之后再来看看 go.mod 文件的情况。 ~~~go module backend go 1.12 require ( github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 // indirect github.com/gin-gonic/gin v1.3.0 github.com/golang/protobuf v1.3.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/ugorji/go v1.1.4 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect ~~~ require 就是 gin 框架所需要的所有依赖包 并且在每个依赖包的后面已经表明了版本号 同时多了一个`go.sum`的文件 go.sum不是一个锁文件,是一个模块版本内容的校验值,用来验证当前缓存的模块。go.sum包含了直接依赖和间接依赖的包的信息,比go.mod要多一些 # 升级依赖项 首先我们需要查看以下我们使用到的依赖列表 ~~~go > $ go list -m all // 你会看到所有项目使用的依赖包 backend github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 github.com/gin-gonic/gin v1.3.0 github.com/golang/protobuf v1.3.1 github.com/mattn/go-isatty v0.0.7 github.com/ugorji/go v1.1.4 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 ~~~ 因为这里使用的是最新的版本,无法升级,所以这里给出一个回退的例子。将 GIN 框架的版本回退到上个版本。这里需要使用一个命令查看依赖的版本历史。 ~~~go >$ go list -m -versions github.com/gin-gonic/gin // 将会列出 Gin 版本历史 github.com/gin-gonic/gin v1.1.1 v1.1.2 v1.1.3 v1.1.4 v1.3.0 ~~~ 将版本更新到上个版本,这里只是个演示。 ~~~go >$ go get github.com/gin-gonic/gin@v1.1.4 // 只需要在依赖后面加上 @version 就可以了 >$ go list -m all // 看到了版本变化 github.com/gin-gonic/gin v1.1.4 ~~~ 或者可以使用`go mod`来进行版本的切换,这样就需要两个步骤了 ~~~go >$ go mod edit -require="github.com/gin-gonic/gin@v1.1.4" // 修改 go.mod 文件 >$ go tidy //下载更新依赖 ~~~ `go.tidy`会自动清理掉不需要的依赖项,同时可以将依赖项更新到当前版本 使用起来这是一个很简单过程,只需要几个命令,你便可以知道依赖的版本信息,以及自由选择安装的版本,一切都变得这么简单。 # 删除未使用的依赖项 如果你在项目过程需要移除一些不需要的依赖,可以使用下面的命令来执行: ~~~go >$ go mod tidy ~~~ 更多关于 go mod 的使用命令 ~~~go >$ go mod 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 ~~~ # 常用命令 有四种指令:module,require,exclude,replace。 * module:模块名称 * require:依赖包列表以及版本 * exclude:禁止依赖包列表(仅在当前模块为主模块时生效) * replace:替换依赖包列表 (仅在当前模块为主模块时生效) 可以使用命令`go list -m -u all`来检查可以升级的package,使用`go get -u need-upgrade-package`升级后会将新的依赖版本更新到go.mod \* 也可以使用`go get -u`升级所有依赖 ~~~ go mod tidy //拉取缺少的模块,移除不用的模块。 ~~~ ~~~ go mod download //下载依赖包 ~~~ ~~~ go mod graph //打印模块依赖图 ~~~ ~~~ go mod vendor //将依赖复制到vendor下 ~~~ ~~~ go mod verify //校验依赖 ~~~ ~~~ go mod why //解释为什么需要依赖 ~~~ ~~~ go list -m -json all //依赖详情 ~~~ # 使用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 ~~~ # idea中配置 goproxy.io 谷歌官方的代理地址,当然还有很多国内优秀的第三方代理。 你也可以在 Goland编辑器中设置 # 主要版本升级 上面版本规则说了,版本 0 和 1,即大版本更新,最好需要有不同的依赖路径,如:v1.0.0 和 v2.0.0 是有不同的依赖路径,那么用 go modules 怎么实现呢,我们可以通过修改 go.mod 文件第一行添加新路径: ~~~ $ cd testmod $ echo 'module github.com/objcoding/testmod/v2' >> go.mod ~~~ 然后我们修改 testmod 函数 Hi(): ~~~ $ cd testmod $ echo 'package testmod import ( "fmt" ) func SayHello(name, str string) string { return fmt.Sprintf("你好, %s, %s", name, str) }' >> testmod.go ~~~ 这时,SayHello() 方法将不兼容 v1 版本,我们需要新建一个 v2.0.0 版本,还是老样子,我们最好在 v2.0.0 版本新建一条 v2 分分支,将 v2.0.0 版本的代码写到这条分支中(这只是一个规范,实际上你将代码也写到任何分支中都行,Go并没有这个规范): ~~~ $ git add * $ git checkout -b v2 $ git commit testmod.go -m "v2.0.0" $ git tag v2.0.0 $ git push --tags origin v2 ~~~ 这时候 testmod 的版本已经更新到 v2.0.0 版本了,该版本并不兼容以前的版本,但是目前项目依然只能获取到 v1.0.1 的依赖版本,因为我们 testmod 的 module 路径已经加上 v2 了,因此并不会出现冲突的问题,那么如果我们需要使用 v2.0.0 版本呢?我们只需要在项目中更改一下 import 路径: ~~~ package main import ( "fmt" "github.com/objcoding/testmod/v2" ) func main() { fmt.Println(testmod.SayHello("张乘辉", "最近过得怎样")) } ~~~ 执行: ~~~ go mod tidy ~~~ 这时我们把 testmod 依赖版本号更新到了 v2.0.0 版本了,虽然是此时的 import 路径是以 “v2” 结尾,但是 Go 很人性化,我们依然可以使用 testmod 来使用。 Go 团队表示,在 Go 1.12 之前,这个特性都将会处于实验性阶段,Go 团队会努力保持兼容性。一旦模块稳定之后,对 GOPATH 的支持将会被移除掉