由于 Go 的版本在不断的更新,其依赖管理的方式也在改变。在引入 Go Modules 之前和之后的依赖管理并不一致,现在更多的使用 Go Modules,但旧的包管理方式仍然存在。
新版本的 Go(Go version >= 1.11)会默认启用 Go Modules,使用旧版的包管理需要手动关闭 Go Modules。
1 | export GO111MODULE=off |
在很多的教程中会建议我们将 Go 的源代码以以下结构进行组织。
1 | GOPATH/ |
其中的 src/
文件夹下存储源代码。在没有 Go Modules 时,当我们进行 import
,Go 会在 GOROOT
和 GOPATH
下查找对应的包。因此,为了便于引用自定义的包(例如上面的 mypackage/
),很多的教程会建议我们将代码写在 GOPATH/src/
下。
GOROOT
存储 Go 的编译器与标准库文件。
除了 src/
文件夹,bin/
存储了编译后的可执行文件,pkg/
存储了编译后的包。在上面的结构中,当我们在 myproject/
中执行 go install
后,可以在 bin/
中发现一个新的可执行文件 bin/myproject
。另外,当我们在 mypackage/
中执行 go install
后,我们会在 pkg/
文件夹中发现一个新的文件 pkg/linux_amd64/myproject/mypackage.a
,这里的 .a
文件是预编译的包文件,可以加速编译,不可单独执行。
在引入了 Go Modules 进行包管理后,GOPATH/src/
下的包会被忽略,但我们可以在任意的地方放置 Go 源码。这并不意味着 GOPATH
没有用了,GOPATH/pkg/mod/
存储不同版本的模块依赖,GOPATH/bin/
存储 Go tools 的可执行文件(例如:gopls
等)。
module 是一系列 package 的集合,带有版本控制与依赖追踪,并且在根目录有 go.mod
文件。
go.mod
文件定义了 module 的名称,同时也含有 module 所需的依赖,每个依赖包含有特定的 module 路径及版本信息。
在一个带有 go.mod
文件的项目中,对于外部引入的 package,Go 检查的顺序如下为:
GOROOT/src/
)./
)GOPATH/pkg/mod/
)GOPROXY/
)例如,我们有以下项目结构。
1 | myproject/ |
main.go
及 go.mod
文件中有以下内容。
1 | /* main.go */ |
当我们在 myproject/
下执行 go run main.go
时,会依次引入 import
所需的包。其中 "github.com/google/uuid"
首先会在本项目文件夹中查询,在查找失败后会转向 go.mod
中得到对应的包及版本信息 v1.6.0
,然后在本地 module cache(GOPATH/pkg/mod/
)中查找 GOPATH/pkg/mod/github.com/google/uuid@v1.3.0
是否存在。
注意到,上面的例子假设了一个前提,即本地存在所需的 package,那如果没有呢?
此时需要通过 GOPROXY
从网络中查询并下载。我们可以使用 go mod tidy
自动获取(详见下节)或是 go get xxx
指定获取 package 并更新 go.mod
。Go 对于没有指定版本的 package 总是会自动拉取 lastest stable version,并将当前的版本信息存入 go.mod
中(例如当前 github.com/google/uuid
lastest stable version 为 v1.6.0)。
另外,我们有时会在某些 go.mod
发现如下语句。
1 | replace github.com/google/uuid => ../local_uuid |
replace
对包解析进行重定向。在 go.mod
文件中 replace
优先级高于 require
,多个 replace
会依次执行。
使用 go mod init <module-name>
初始化 go.mod
文件。
使用 go mod tidy
命令可以实现:1. 移除未使用依赖;2. 添加缺失依赖;生成/更新 go.sum
文件。go.sum
文件存储引入模块的校验和,保证模块的完整性与一致性,示例如下。
1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= // module integrity |
在引入 Go Modules 功能后,最大的优势在于可以更好的进行依赖管理。在传统的 GOPATH
包管理模式下,我们要控制不同项目所使用包的版本是很困难的,实在是缺乏灵活性。
Go Modules 的引入大大方便了不同版本包的依赖管理,让代码从 GOPATH/src/
中解放,同时提供了便利的 go mod
命令实现自动化管理。
对于自己写的 module,建议的命名方式为 github.com/username/modulename
,这样可以保证 Go 远程拉取的正确执行。
在写自己的 Module 时需要考虑版本控制的问题,版本命名一般遵循顺序为 v[Major].[Minor].[Patch]
。
对于 Minor
及 Patch
的更新,我们需要做的是使用 git tag v1.x.x
将小版本更新打上 tag 并推送(本地或是远程均可),实现对不同版本的区分。
而对于 Major
的大版本更新,我们需要将其视为不同的 package,示例如下。
1 | import "github.com/google/uuid" // v1.x.x |
因此,我们在开发时,对于大版本的更新,不仅需要对应的给出 tag,还需要更改 module 的名称,即更新 go.mod
中的模块名。