layout: post title: "Go Modules详解" categories: Go tags: modules
在默认情况下,$GOPATH 默认情况下是不支持 go mudules 的,我们需要在项目目录下手动执行以下命令:
$ export GO111MODULE=on
这也表明了 go 要利用 modules 机制消灭 $GOPATH 的决心啊!
为了配合 go modules 机制,我们 $GOPATH 以外的目录创建一个 testmod 的包:
$ 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 了。
$ 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 来使用。