Golang Modules学习

转载请注明出处:www.huamo.online

Modules概念

一个module是相关Go package的一个集合。modules是源码交互和版本控制的单元。go命令直接支持使用模块,包括记录和解析对其他模块的依赖。modules替代了以前基于GOPATH的方式来指定在构建时使用哪些源文件。

Go 1.11开始初步支持modules这一崭新概念,它统一支持包依赖的版本和路径,版本依赖信息变得轻量而且明确,构建也变得更加可靠和可复现。

module作为一个集合存放在一个以go.mod文件为根的文件树中。go.mod文件定义了模块的module path,这个路径同时也是相对于根目录($GOPATH/src)的import path。还定义了这个模块的依赖需求,即为了完成构建需要用到的其它模块。每一个依赖需求都写作一个模块路径加上一个特定的语义化版本

从Go 1.11开始,如果一个工程目录在$GOPATH/src外面,且当前目录或任何父目录有go.mod文件,那么go命令就会启用modules(如果工程在$GOPATH/src内部,为了兼容性,go命令依然使用旧的基于GOPATH构建模式,即使找到了go.mod文件)。从Go 1.13开始,module模式会作为所有开发的默认构建模式。

module的初步支持

Go 1.11初步支持modules,包含了一个新的可以感知modulego get命令。开发组会一直保持更新,直到官方宣布正式支持以后,然后就会移除掉GOPATH构建模式以及老的go get命令。

最快最简单的使用新版module构建项目的方式是:将工程目录移到$GOPATH/src外面,并在目录下创建一个go.mod文件,然后在目录下运行go命令。

为了更细粒度的控制,Go 1.11提供了一个临时的环境变量GO111MODULE。可以设置为3个值:off, on, auto(默认值)。如果GO111MODULE=off,那么go命令永不使用module支持,而是像以前一样查看vendor文件夹和$GOPATH目录来寻找依赖(这被称为GOPATH模式)。如果GO111MODULE=on,那么go命令就会要求使用modules特性,而永不再考虑$GOPATH(这被称为module-aware模式)。如果GO111MODULE=auto或未设置,go命令则会依据当前目录来启用或关闭module特性。仅当工程目录在$GOPATH/src外面且本身有go.mod文件或父目录有这个文件,才启用module支持。

module-aware模式下,构建时$GOPATH不再定义import的含义,但它会存储下载好的依赖(存在$GOPATH/pkg/mod),以及安装的可执行命令(一般存在$GOPATH/bin,除非系统设置了$GOBIN)

创建一个新的module

  • 将工程目录创建在$GOPATH/src外面,正常编写代码文件和单元测试代码文件
1
2
3
4
$ pwd
~/outgopath/gopher/hello
$ ls
hello.go hello_test.go
  • 将工程目录作为module的根目录
1
2
3
4
5
6
7
8
9
10
$ go mod init gopher/hello
go: creating new go.mod: module gopher/hello
$ pwd
~/outgopath/gopher/hello
$ ls
go.mod hello.go hello_test.go
$ cat go.mod
module gopher/hello

go 1.12

go.mod文件只会出现在module的根目录中。子目录中package的import路径是module路径加上子目录路径即可,且子目录不需要再运行go mod init命令。例如hello/有个子目录world,那么它的import路径就是gopher/hello/world

添加一个依赖

创造Go modules的主要目的就是为了提高使用其他代码的体验(即添加依赖)

当遇到import了一个go.mod中没有提供的packagego命令就会自动寻找包含这个packagemodule并将其添加到go.mod中,使用latest版本。所谓的latest规则为:

  1. 最新,打了tag标志稳定的版本(非预发行版本),即x.y.z

  2. 如果没有,则选最新,打了tag的预发行版本,即x.y.z-abc1

  3. 如果还没有,则选最新,没有打tag的版本。

只有直接引用的依赖才记录在go.mod文件中。但是使用go get升级了的依赖,即使是非直接引用,也会记录在go.mod中,下文会说

检查当前模块和它的所有依赖:

1
2
3
4
5
$ go list -m all
gopher/hello //主module
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

还可以使用go list -m rsc.io...进行模糊搜索

依赖的显示是根据module路径排序

其中golang.org/x/text的是go命令为没有打tag的提交生成的伪版本

go命令还维护了一个go.sum文件,包含了每个依赖的加密哈希校验和。以此确保以后的下载依赖和初次下载的内容完全一致。

go.modgo.sum都需要检入到版本控制中。

升级依赖

可以使用go get -v golang.org/x/text升级这个依赖,这里没有指定升级到的版本,就会默认升级到@latest。完成之后,go.mod就会新增记录,go.sum也会随之更新。即使是非直接依赖,此时也会记录在go.mod

也可以使用go get -v rsc.io/sampler@v1.3.1指定升级版本。

1
2
3
4
5
6
7
8
9
10
11
12
$ go get -v rsc.io/sampler@v1.3.1
$ go get -v golang.org/x/text
$ cat go.mod
module gopher/hello

go 1.12

require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.1 // indirect
)

增加一个新的主版本依赖

就像以太坊中遇到的那样,whisper有v5和v6两个版本,分别对应两个目录,现在我终于明白了为什么会这样分开冗余代码,因为主版本的变更一般都是对外接口不再向后兼容,但是对于一个已经存在的module,别人有可能还在依赖旧版本代码,所以只能以主版本号为文件夹名开辟新的module path,这种惯例称为语义化导入版本

所以,在go import中,可以同时依赖一个模块的多个主版本,例如:

1
2
3
4
5
6
7
8
9
10
$ vim hell.go
...
import (
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
...
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0

这样就可以既使用部分新特性,又能和老版本进行兼容。

移除未使用的依赖

go build或者go test这种命令可以快速发现缺少了什么,但并不能安全的告知什么可以移除,因为移除一个依赖需要在检查完这个module下所有的包之后才能做,包括这些包的所有可能编译参数组合都检查通过才行。

go mod tidy可以完成这个使命

Go包版本提议

介绍

8年前,Go开发组推出了goinstall(最后演化为go get),其去中心化,类似URL的导入路径对Go开发者颇为熟悉。当goinstall发布之后,人们最先询问的问题便是如何纳入版本信息。Go开发组承认:We admitted we didn’t know。长期以来,他们都认为包版本的问题最好是通过一个插件工具来解决,他们也鼓励社区创造出一个这样的工具。于是很多工具用了很多不同方式来解决这个问题,到了2016中间的时候,市面上已经出现了太多解决方案,开发组认为需要选用一个单一的,官方的工具结束这个局面。

参考链接

转载请注明出处:www.huamo.online