Go Modules是Go 1.11引入的,为了解决Go项目的依赖问题。从go 1.16开始,go modules开始成为Go默认的包依赖管理工具。

gomoddownload使用场景(小白是这么理解Go)(1)

go moudle是一组go package的集合

一个Go Module实际上是由一组Go的Package的集合。 构建Go Module的过程需要先确定依赖的其他Go Module的版本、编译包、将编译的目标文件链接到一起。

一个Go项目就是使用go mod创建的一个Go Module,由多个Go package组成,这个项目被保存在一个代码库中(如git)。 也就是说这个代码库中有多个go的package,如https://github.com/grpc/grpc-go这个项目。 每个Go Module根目录中会有一个go.mod文件描述module的元数据信息。

看一下grpc-go这个项目的目录结构:

./grpc-go ├── Documentation ├── admin ├── attributes ├── authz ├── backoff ├── balancer ├── benchmark ├── binarylog ├── channelz ├── cmd ├── codes ├── connectivity ├── credentials ├── encoding ├── examples ├── grpclog ├── health ├── internal ├── interop ├── keepalive ├── metadata ├── peer ├── profiling ├── reflection ├── resolver ├── security ├── serviceconfig ├── stats ├── status ├── stress ├── tap ├── test ├── testdata ├── xds ... ... (省略了根目录里的一些go源码和其他的一些文件) ... ├── server.go ├── go.mod ├── go.sum └── xds

可以看到grpc-go这个go module就是由一组go pacakge组成,在根目录里有一个go.mod文件,内容如下:

module google.golang.org/grpc go 1.14 require ( github.com/cespare/xxhash/v2 v2.1.1 github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/protobuf v1.4.3 github.com/google/go-cmp v0.5.0 github.com/google/uuid v1.1.2 golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 google.golang.org/protobuf v1.25.0 )

go module基于语义化版本

go moudle基于语义化版本,采用v<major>.<minor>.<patch>的格式,例如grpc-go依赖的github.com/google/uuid这个go moudle的版本是v1.1.2。 语义版号的递增规则如下:

在语义化版本的规范下,使用github.com/google/uuid这个外部依赖的话,grpc-go选择任何v1.x.y都是兼容现在的代码的,例如后续的v1.1.3, v1.2.0。 如果有一天github.com/google/uuid发布了v2.0.0的版本,grpc-go直接修改了go.mod升级了依赖的版本到v2.0.0,如果按照前面的go module依赖导入规则,就会出现编译错误,因为主版本号升级后不承诺API的兼容性。 针对这个问题Go Module给的解决方案是,从主版本号的2开始将主版本号加入到go moudle的path中,具体规则如下:

语义化版本

module path

导入go moudle中的包

v0.x.y

github.com/google/uuid

import “github.com/google/uuid”

v1.x.y

github.com/google/uuid

import “github.com/google/uuid”

v2.x.y

github.com/google/uuid/v2

import “github.com/google/uuid/v2”

v3.x.y

github.com/google/uuid/v3

import “github.com/google/uuid/v3”

这样如果将项目依赖的外部go moudle的主版本号升级时,就需要切换moudle path和代码中导入package路径,同时对使用的不兼容的API做出修改调整。

关于传递依赖如何选择,例如我们正在开发的A moudle同时依赖了B module和C moudle,而B依赖v1.1.0的D,C依赖v1.2.0的D,D的最新版本是v1.3.0。 使用go mod时,项目A通过传递依赖使用的C的版本将会是v1.2.0。因为按照语义化版本,主版本号相同的话,次版本号越大的其稳定性和安全性应该是更好的,由于B和C传递的版本中选择了次版本号更大的版本。

参考,