go modules 是 golang 1.11 新加的特性,go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。
Go Module使用先决条件
-
Golang版本 >= 1.11
-
设置GO111MODULE
GO111MODULE设置
GO111MODULE 有三个值:off, on和auto(默认值)。
-
GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。 -
GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。 -
GO111MODULE=auto,默认值,在$GOPATH/src之外的目录中调用 go 命令,且当前目录或其任何父目录中使用有效的go.mod文件,go命令会启动modules。
当modules 功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的 module。
Go Mod 包管理工具
golang 提供了 go mod命令来管理包。
| 命令 | 说明 |
|---|---|
| download | 下载依赖包 |
| edit | 编辑go.mod |
| graph | 打印模块依赖图 |
| init | 在当前目录初始化mod |
| tidy | 拉取缺少的模块,移除不用的模块 |
| vendor | 将依赖复制到vendor下 |
| verify | 验证依赖是否正确 |
| why | 解释为什么需要依赖 |
如何在项目中使用
- 初始化
在项目根目录执行以下命令
$ mkdir hello-go
$ cd hello
$ go mod init hello-go
会在hello目录下,生成go.mod的文件
module example.com/hello
go 1.14
- 更新go.mod
创建示例代码server.go,可以使用两种方式更新go.mod中的依赖
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
**方式一:**在项目根目录执行
$ go run server.go
或者
$ go run ./...
./... 模式匹配当前模块中的所有包
**方法二:**执行go get 命令更新依赖
$ go get -u github.com/labstack/echo
go.mod 文件说明
示例go.mod
module example.com/hello
require (
github.com/labstack/echo v3.3.10+incompatible // indirect
github.com/labstack/gommon v0.2.8 // indirect
github.com/mattn/go-colorable v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/valyala/fasttemplate v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect
)
go 1.14
go.mod 文件中的命令
go.mod 提供了module, require、replace和exclude 四个命令:
-
module语句指定包的名字(路径) -
require语句指定的依赖项模块 -
replace语句可以替换依赖项模块 -
exclude语句可以忽略依赖项模块
go semver(语义化版本)
golang 官方推荐的最佳实践叫做 semver,这是一个简称,写全了就是Semantic Versioning,也就是语义化版本,如v0.1.1 ,版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。
- 语义化版本的好处
semver 简化版本指定的作用是显而易见的,然而仅此一条理由显然有点缺乏说服力,通过semver对版本进行严格的约束,可以最大程度地保证向后兼容以及避免 “breaking changes”,而这些都是 golang 所追求的。两者一拍即合,所以 go modules 提供了语义化版本的支持。
如果你使用和发布的包没有版本tag或者处于v1.x版本,那么你可能体会不到什么区别,主要的区别体现在v2.0.0以及更高版本的包上。按照语义化版本的约定,当出现v2.0.0的时候一定表示发生了重大变化,很可能无法保证向后兼容,这时候应该如何处理呢?答案很简单,我们为包的导入路径的末尾附加版本信息即可,例如:
module my-module/v2
require (
some/pkg/v2 v2.0.0
some/pkg/v2/mod1 v2.0.0
my/pkg/v3 v3.0.1
)
格式总结为pkgpath/vN,其中N是大于1的主要版本号。在代码里导入时也需要附带上这个版本信息,如import "some/pkg/v2"。如此一来包的导入路径发生了变化,也不用担心名称相同的对象需要向后兼容的限制了,因为golang认为不同的导入路径意味着不同的包。
不过这里有几个例外可以不用参照这种写法:
-
当使用
gopkg.in格式时可以使用等价的require gopkg.in/some/pkg.v2 v2.0.0 -
在版本信息后加上
+incompatible就可以不需要指定/vN,例如:require some/pkg v2.0.0+incompatible -
使用go1.11时设置
GO111MODULE=off将取消这种限制,当然go1.12里就不能这么干了
除此以外的情况如果直接使用v2+版本将会导致go mod报错。
v2+版本的包允许和其他不同大版本的包同时存在(前提是添加了/vN),它们将被当做不同的包来处理。另外/vN并不会影响你的仓库,不需要创建一个v2对应的仓库,这只是go modules添加的一种附加信息而已。当然如果你不想遵循这一规范或者需要兼容现有代码,那么指定+incompatible会是一个合理的选择。不过如其字面意思,go modules不推荐这种行为。
最后总结一下go包升级过程:
当你发布一个v2+版本的库时,需要进行以下操作:
-
将
module my-module改成module my-module/v2 -
将源代码中使用了v2+版本包的import语句从
import "my-module"改为import "my-module/v2" -
仔细检查你的代码中所有my-module包的版本是否统一,修改那些不兼容的问题
-
在changelog中仔细列出所有breaking changes
**注意:**如果你觉得前面四步过于繁琐,注明你的用户需要指定+incompatible是一个暂时性的解决方案。
go.mod中的两种依赖版本格式
- 语义化(semver)版本git tag
github.com/labstack/gommon v0.2.8
- 伪版本
如果一个 module 没有有效的 semver 版本,那么 go.mod 将通过一个叫做 “伪版本“ 的东西来记录版本。
”伪版本“ 的通常形式是 vX.0.0-yyyymmddhhmmss-abcdefabcdef。 比如:
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
其中: v0.0.0 表示 semver 版本号, 20170915032832 表示这个版本的时间。 14c0d48ead0c 表示这次提交的 git hash。
如何解决依赖下载失败的问题
由于某些已知的原因,并不是所有的package都能成功下载,比如:golang.org下的包
在go.mod文件中使用replace命令
modules 可以通过在 go.mod 文件中使用 replace 指令替换成github上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
设置GOPROXY
Go在1.13加入了Go Proxy的支持
export GOPROXY=https://goproxy.io
或者
export GOPROXY=https://goproxy.cn
- GOPROXY说明
GOPROXY指镜像递增和获取顺序,每个镜像URL之间用,隔开,按照优先顺序排序,**direct**表示源站。
默认值:
GOPROXY="https://proxy.golang.org,direct"
这是官方的说法:
The default setting for GOPROXY is
"proxy.golang.org,direct", which means to try the
Go module mirror run by Google and fall back to a direct connection
if the proxy reports that it does not have the module (HTTP error 404 or 410).
注意 :GOPROXY=off 表示“禁止从互联网下载任何依赖”,GOPROXY=direct 表示“依赖一律从源站下载”。
对go.sum文件的理解
也许你知道npm的package-lock.json的作用,它会记录所有库的准确版本,来源以及校验和,从而帮助开发者使用正确版本的包。通常我们发布时不会带上它,因为package.json已经够用,而package-lock.json的内容过于详细反而会对版本控制以及变更记录等带来负面影响。
如果看到go.sum文件的话,也许你会觉得它和package-lock.json一样也是一个锁文件,那就大错特错了。go.sum不是锁文件。
更准确地来说,go.sum是一个构建状态跟踪文件。它会记录当前module所有的顶层和间接依赖,以及这些依赖的校验和,从而提供一个可以100%复现的构建过程并对构建对象提供安全性的保证。
go.sum同时还会保留过去使用的包的版本信息,以便日后可能的版本回退,这一点也与普通的锁文件不同。所以go.sum并不是包管理器的锁文件。
因此我们应该把go.sum和go.mod一同添加进版本控制工具的跟踪列表,同时需要随着你的模块一起发布。如果你发布的模块中不包含此文件,使用者在构建时会报错,同时还可能出现安全风险(go.sum提供了安全性的校验)。
如何在go modules中使用vendor目录
golang一直提供了工具选择上的自由性,如果你不喜欢go mod的缓存方式,你可以使用go mod vendor回到godep或govendor使用的vendor目录进行包管理的方式。
当然这个命令并不能让你从godep之类的工具迁移到go modules,它只是单纯地把go.sum中的所有依赖下载到vendor目录里,如果你用它迁移godep你会发现vendor目录里的包回合godep指定的产生相当大的差异,所以请务必不要这样做。
- 如何进行编译
使用go build -mod=vendor来构建项目,因为在go modules模式下go build是屏蔽vendor机制的,所以需要特定参数-mod=vendor重新开启vendor机制。
一些常用的命令
-
go get -u github.com/some/pkg更新次版本号,由于主版本号的不兼容,所以不会更新主版本号。 -
go get -u=patch更新修订号 -
go list -m all查看所有依赖的 module 以及版本 -
go list -u -m all查看可用的次版本号和修订号的更新 -
go mod tidy删除 go.mod 中没用到的 module