Go1.11 新特性 — 依赖管理工具:Go Modules 使用过程学习笔记,内容包括:

  • 如何在 的项目上使用 go modules
  • 如何在 的项目中引入 go modules

Statement: 文中部分内容参考自网络博客(地址在文末),并结合自己的学习过程稍加整理和总结,欢迎广大 Gophers 交流指正 🙂

上一篇 文章中对 Go Module 相关概念做了简单学习和了解,接下来学习一下 Go Module 的具体使用方式吧, So, Go.

实践环境:go version go1.11 windows/amd64

1. Go Module 初始化

Note: Go Module 和传统的 GOPATH 不同,不需要包含例如:src、bin 这样的子目录,一个源代码目录甚至是空目录都可以作为 module,只要其中包含 go.mod 文件即可。

Next, 我们先用一个空目录(GOPATH之外)来创建第一个包含 Go Module 的项目吧!

1.1. 新建项目

要初始化 module, 需要使用如下命令:

1
$ go mod init [module]

1.1.1. 创建我们的测试项目:demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 首先查看一下,当前目录是在 GOPATH 之外
$ echo $GOPATH
D:\code\Go_Path

$ pwd
/d/code/Temp_Code/src/demo.go.modules

# 初始化 Module
$ go mod init demo

$ ls
go.mod

# Note: 命令执行完成后会在当前目录下生成一个 go.mod 文件,里面只有一行内容 `module demo`

1.1.2. 创建用于测试的 Go 源码文件:main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 可以看到该文件只包含了一个依赖包:"github.com/sirupsen/logrus"

package main

import (
    "sync"

    "github.com/sirupsen/logrus"
)

type Cache struct {
    sync.Map
}

func main() {
    cache := Cache{Map:sync.Map{}}

    cache.Store("i", "1")
    cache.Store("j", "2")
    cache.Store("k", "3")

    cache.Range(func(key, value interface{}) bool {
    	logrus.Infoln(key, value)
    	return true
    })
}

1.1.3. 构建模块

当我们使用 go buildgo test 以及 go list 时,Go 会自动更新 go.mod 文件,并且将依赖关系写入其中。

1
2
3
4
5
6
7
$ go build ./...
go: finding github.com/sirupsen/logrus v1.0.6
go: finding golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac
go: finding golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b
go: downloading github.com/sirupsen/logrus v1.0.6
go: downloading golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac
go: downloading golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b

可以看到 go 自动查找了依赖并完成下载,但是下载的依赖包并不是下载到了 $GOPATH 中,而是在 $GOPATH/pkg/mod 目录下,且多个项目可以共享缓存的 module

然后,我们来看看当前目录发生了什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ ls
demo.exe  go.mod  go.sum  main.go

# demo.exe 不用多说,是编译生成的可执行文件

# 在来看看 go.mod 里面的变化
$ cat go.mod
module demo

require (
        github.com/sirupsen/logrus v1.0.6
        golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
        golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b // indirect
)
# Note: go.mod 中记录了我们项目的直接或间接引用的包

# 另外,多了一个 go.sum 文件,我们来看看里面是什么内容
$ cat go.sum
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b h1:cmOZLU2i7CLArKNViO+ZCQ47wqYFyKEIpbGWp+b6Uoc=
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

# Note: 在 go.sum 是我们直接引用的 package 和它自身需要的依赖的版本记录,go module 就是根据这些去找对应的 package 的。

顺带一提,如果我们不做任何修改,默认会使用最新的包版本,如果包打过tag,那么就会使用最新的那个tag对应的版本。

现在来说说如何定义一个modules,modules 是由Go源文件目录结构定义的,如果目录下含有 go.mod 文件,该目录称为模块根目录 (module root)。模块根目录及其子目录所有的 Go 包都是属于该 modules 的,但是如果子目录包含有了自己的 go.mod 文件,就不再隶属于父 module 下, 而是有了自己的 module。 举一个例子:(这部分内容参考链接)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)
$ tree
.
|-- README.md
|-- go.mod
|-- go.sum
|-- models.go
|-- mq_interface.go
|-- sarama
|   |-- sarama_consumer.go
|   |-- sarama_consumer_test.go
|   |-- sarama_producer.go
|   `-- sarama_producter_test.go
`-- segmentio
    |-- segmention_Consumer.go
    |-- segmention_consumer_test.go
    |-- segmention_producer.go
    `-- segmention_producter_test.go

gitlab.luojilab.com/zeroteam/ddkafka 目录下含有了go.mod文件, 所以其子目录 sarama 和segmentio 都属于 gitlab.luojilab.com/zeroteam/ddkafka 模块,但是如果在 segmentio 目录中加入了go.mod,那么 segmentio 就不再隶属于 gitlab.luojilab.com/zeroteam/ddkafka 模块了。

1.1.3.1. -mod 选项

此外,go build 命令新增加了一个编译选项: -mod

1
2
3
4
5
usage: go build [-o output] [-i] [build flags] [packages]

        -mod mode
            module download mode to use: readonly, release, or vendor.
            See 'go help modules' for more.
1
$ go build -mod=readonly

在这个模式下,任何会导致依赖关系变动的情况都将导致 build 失败,前面提到 build 能自动查找并更新依赖关系,使用这个选项可以测试 go.mod 中的依赖是否整洁,防止隐式修改 go.mod,但如果明确调用了 go mod、go get 命令则依然会导致 go.mod 文件被修改。

1.1.4. 包管理

1.1.4.1. 手动处理依赖

我们可以使用 go mod tidy 手动处理依赖关系,用法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
usage: go mod tidy [-v]

Tidy makes sure go.mod matches the source code in the module.
It adds any missing modules necessary to build the current module's
packages and dependencies, and it removes unused modules that
don't provide any relevant packages. It also adds any missing entries
to go.sum and removes any unnecessary ones.

The -v flag causes tidy to print information about removed modules
to standard error.

执行命令:

1
$ go mod tidy

这条命令会自动添加丢失的或移除不再使用的 modules

1.1.4.2. go get 升级

  • 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)

  • 运行 go get -u=patch 将会升级到最新的修订版本

  • 运行 go get package@version 将会升级到指定的版本号version

运行go get如果有版本的更改,那么go.mod文件也会更改

1.2. 已存在项目

假设你已经有了一个 go 项目, 比如在 $GOPATH/github.com/smallnest/rpcx 下, 你可以使用 go mod init github.com/smallnest/rpcx 在这个文件夹下创建一个空的 go.mod (只有第一行 module github.com/smallnest/rpcx)。

然后你可以通过 go get -m ./…让它查找依赖,并记录在go.mod文件中(你还可以指定 -tags,这样可以把tags的依赖都查找到)。

通过 go mod tidy 也可以用来为 go.mod 增加丢失的依赖,删除不需要的依赖,但是我不确定它怎么处理tags。

执行上面的命令会把 go.mod 的 latest 版本换成实际的最新的版本,并且会生成一个 go.sum 记录每个依赖库的版本和哈希值。

这部分内容参考链接

Note: 在 Go1.11 版本下,GOPATH 目录中的项目默认是禁用 Go Module 的,需要手动开启

2. See Also

Thanks to the authors 🙂

3. Changed Log