微信公众号「后端进阶」,专一后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。
在 Java 的项目中,有 Maven 和 Gradle 这些很好用的依赖版本管理工具,简直不要太方便了,可是在 Golang 的项目中,以前的 Golang 官方并无提供版本管理工具,咱们之前用 go get 获取依赖实际上是有潜在危险的,由于咱们不肯定最新版依赖是否会破坏掉咱们项目对依赖包的使用方式,即当前项目可能会出现不兼容最新依赖包的问题。以后官方出了一个 vendor 机制,将项目依赖的包都放在该目录中,但这也并无很好地管理依赖的版本。以后官方出了一个准官方版本管理工具 go dep,这也算是 go modules 的前身了吧。随着 Go1.11 的发布,Golang 给咱们带来了 module 全新特性,这是 Golang 新的一套依赖管理系统。如今 Go1.12 已经发布了,go modules 进一步稳定,但官方仍是没有将其设为默认机制,因此踩坑之路是必须的,本篇文章除了详细说明 go modules 的特性以及使用以外,还总结了我在这个过程当中遇到的一些“坑”。git
在默认状况下,$GOPATH 默认状况下是不支持 go mudules 的,咱们须要在项目目录下手动执行如下命令:程序员
$ export GO111MODULE=on
这也代表了 go 要利用 modules 机制消灭 $GOPATH 的决心啊!github
为了配合 go modules 机制,咱们 $GOPATH 之外的目录建立一个 testmod 的包:golang
$ mkdir testmod $ cd testmod $ echo 'package testmod import "fmt" func SayHello(name string) string { return fmt.Sprintf("Hello, %s", name) }' >> testmod.go
$ go mod init github.com/objcoding/testmod go: creating new go.mod: module github.com/objcoding/testmod
以上命令会在项目中建立一个 go.mod 文件,初始化内容以下:后端
module github.com/objcoding/testmod
这时,咱们的项目已经成为了一个 module 了。bash
$ git init $ git add * $ git commit -am "First commit" $ git push -u origin master
在这里我也着重说下关于项目依赖包引用地址的问题,这个问题虽小,但也确实很困扰人,因此必须得说一下:微信
go mudules 出现以前,在一个项目中有不少个包,在项目内,有些包须要依赖项目内其它包,假设项目有个包,相对于 gopath 的地址是 objcoding/mypackage,在项目内其它包引用这个包时,就能够经过如下引用:框架
import myproject/mypackage
但你有没有想过,当别的项目须要引用你的项目中的某些包,那么就须要远程下载依赖包了,这时就须要项目的仓库地址引用,好比下面这样:编辑器
import github.com/objcoding/myproject/mypackage
go modules 发布以后,就彻底统一了包引用的地址,如上面咱们说的建立 go.mod 文件后,初始化内容的第一行就是咱们说的项目依赖路径,一般来讲该地址就是项目的仓库地址,全部须要引用项目包的地址都填写这个地址,不管是内部之间引用仍是外部引用,举个例子,goim 的内部包引用:分布式
go.mod module:
内部包引用:
也便是说,在项目 启用了 go modules 以后,引用包必须跟 go mod 文件第一行包名同样,
依赖的包都会保存在 ${GOPATH}/pkg/mod 文件夹中了,咱们也能够在项目底部那里查看依赖包:
但也有可能会出现依赖包地址正确但会报红的状况,这时极有多是你在 Goland 编辑器中没有将项目设置为 go modules 项目,具体设置以下:
勾选了该选项以后,就会在 External Libraries 中出现 Go Modules 目录。
go modules 是一个版本化依赖管理系统,版本须要遵循一些规则,好比版本号须要遵循如下格式:
vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef vX.0.0-yyyymmddhhmmss-abcdefabcdef vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef vX.Y.Z
vX.Y.Z 是咱们仓库打的标签版本,也就是 go modules 是根据仓库标签来肯定版本号的,所以咱们发布版本时,须要给咱们的仓库打上一个标签版本号。
也就是版本号 + 时间戳 +hash,咱们本身指定版本时只须要制定版本号便可,没有版本 tag 的则须要找到对应 commit 的时间和 hash 值。
还有一个重要的规则是,版本 0 和 1,最好须要有不一样的依赖路径,如:v1.0.0 和 v2.0.0 是有不一样的依赖路径,下面会详细介绍一下这个版本规则。
了解了 go modules 的版本规则后,如今咱们发布一下该项目的版本:
$ git tag v1.0.0 $ git push --tags
这时咱们最好还须要建立一条 v1 分支,以便咱们在其它分支写代码不会影响到 v1.0.0 版本:
$ git checkout -b v1 $ git push -u origin v1
如今咱们把刚刚作好的 module,拿过来用,新建一个 gomodules 项目:
package main import ( "fmt" "github.com/objcoding/testmod" ) func main() { fmt.Println(testmod.SayHello("张乘辉")) }
之前咱们能够直接经过 go get 命令获取依赖包,可是对于一个 module 项目来讲,就远远比这个有趣多了,现将项目初始化成 module 项目:
$ go mod init
这时 go build 等命令就会下载依赖包,并把依赖信息添加到 go.mod 文件中,同时把依赖版本哈希信息存到 go.sum 文件中:
$ go build go: finding github.com/objcoding/testmod v1.0.0 go: downloading github.com/objcoding/testmod v1.0.0
这时,go.mod 文件内容以下:
module gomodules require github.com/objcoding/testmod v1.0.0
go.sum 文件内容以下:
github.com/objcoding/testmod v1.0.0 h1:fGa15gBXoqkG0BVkQGP9i5Pg2nt8nayFpHFf+GLiX6A= github.com/objcoding/testmod v1.0.0/go.mod h1:LGpYEmOLZhLQC3JW88STU2Ja3rsfoGZmsidsHJhDNGU=
这里还须要注意的是,有时候咱们引用 golang.org/x/ 的一些包,但发如今伟大的天朝这个地址是被qian了,可是咱们程序员也充分发挥了勤奋好学的优良传统,在 go modules 中设置了 goproxy 机制,若是 go modules 设置了代理,会优先从代理中下载依赖包,在 /etc/profile 中加入如下内容:
export GOPROXY="https://goproxy.io"
goproxy.io 谷歌官方的代理地址,固然还有不少国内优秀的第三方代理。
你也能够在 Goland编辑器中设置:
如今咱们来升级一下 testmod 项目:
$ cd gomodules $ echo 'package testmod import "fmt" func SayHello(name string) string { return fmt.Sprintf("你好, %s", name) }' >> testmod.go
我把「Hello」改为「你好」,咱们把这个修改在 v1 分支中进行:
$ git commit -m "update testmod" testmod.go $ git tag v1.0.1 $ git push --tags origin v1
如今咱们的 项目已经升级到 v1.0.1 版本了,咱们能够有多种方式获取这个版本依赖,go1.11 中,go get 拥有了不少新特性,咱们能够直接经过如下命令获取 v1.01 版本依赖:
$ go get github.com/objcoding/testmod@v1.0.1
也能够经过 go mod 命令:
$ go mod edit -require="github.com/objcoding/testmod@v1.0.1" $ go mod tidy
go mod edit -require 能够主动修改 go.md 文件中依赖的版本号,而后经过 go mod tidy 对版本进行更新,这是一条神同样的命令,它会自动清理掉不须要的依赖项,同时能够将依赖项更新到当前版本。
上面版本规则说了,版本 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 来使用。