叩町

叩町

【GO】Go的依赖管理

go
5
2025-12-18
【GO】Go的依赖管理

构建一个Go程序的过程包括确定依赖包及其版本、编译这些包以及将编译后的目标文件链接在一起。

Go依赖管理模式经历的三个阶段:

  1. GOPATH模式:编译器尝试在$GOPATH/src目录下寻找第三方依赖包,仅获取当前依赖包在获取时的最新版本,不关注具体的某个版本,会造成一些构建问题。
  2. Go1.5版本推出的vendor机制:优先从vendor目录下寻找缓存的依赖包版本,解决了依赖包的不兼容升级导致的构建问题。
  3. Go module:自动处理包依赖关系,依赖管理更加完善。

基于Go module的依赖管理

基于已有项目创建并构建一个Go module,通常包括以下几个步骤:

  1. 使用go mod init命令创建go.mod文件,将当前项目转变为一个Go module。
  2. 使用go mod tidy命令自动更新当前module的依赖信息。
  3. 执行go build命令构建新的module。

例如在某个空目录下创建main.go文件并写入以下内容:

// ch5/modulemode/main.go
package main
import "github.com/hanxuanyu/logrus"
func main() {
    logrus.Println("hello, go module mode")
}

在这个项目下执行:

go mod init github.com/hanxuanyu/module-mode

执行完成后,目录下出现一个go.mod文件,内容如下:

module github.com/hanxuanyu/module-mode

go 1.24.11

执行以下命令后会自动下载依赖:

go mod tidy

这个命令会分析所有源文件,确定直接依赖包和间接依赖包的名称和具体版本,并下载相应的依赖包。依赖会被放置在本地module的缓存路径下,默认为:$GOPATH[0]/pkg/mod,并且自Go1.15起,可以设置GOMODCACHE环境变量来自定义缓存路径。

GOPROXY环境变量可以为依赖的下载提供代理,默认值为https://proxy.golang.org,direct,可以修改为更适合国内使用的代理服务:https://goproxy.cn,direct

此时当前的go.mod文件内容如下:

module github.com/hxuanyu/module-mode

go 1.24.11

require github.com/sirupsen/logrus v1.9.3

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

同时新增了一个go.sum文件:

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

这两个文件将程序所使用到的依赖包的版本和对应的文件hash值进行固化,当其他人在使用我们发布的源码进行编译时,可以确保构建得到完全一致的构建产物,不会因为依赖版本差异导致不可预知的问题,因此,这两个文件也建议添加到版本管理工具中。

此时执行:

go build

Go编译器会根据源码和依赖包进行构建,并生成一个可执行程序module-mode(Windows下为module-mode.exe),执行这个程序就能得到预期结果。

常见的Go module操作

添加依赖

在代码中需要引入新的第三方包时,可以使用以下步骤添加依赖:

  1. 更新源码导入新包:
package main
import (
    "github.com/google/uuid" // 新增的依赖包
    "github.com/sirupsen/logrus"
)
func main() {
    logrus.Println("hello, go module mode")
    logrus.Println(uuid.NewString())
}
  1. 使用go get命令添加依赖

执行命令后,不仅在本地缓存中出现了新增的依赖包,go.mod文件也被新增了对应的依赖信息:

module github.com/hxuanyu/module-mode

go 1.24.11

require (
	github.com/google/uuid v1.6.0
	github.com/sirupsen/logrus v1.9.3
)

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect


手动执行go get命令添加依赖与执行go mod tidy命令自动分析并下载依赖项的最终效果是等价的,但是在处理复杂项目的变更时,逐一手动添加依赖项效率低下,使用go mod tidy是更佳的选择。

升级/降级依赖的版本

Go module对版本号的要求:

vX.Y.Z


v:前缀

X:主版本号

Y:次版本号

Z:补丁版本号

按照规范,不同主版本号的两个版本是互不兼容的;在主版本号相同的情况下,较高版本号的版本通常向后兼容较低版本号的版本;补丁版本号的变化不影响兼容性。

此外,Go module规定,如果同一个包的新旧版本是兼容的,那么它们的导入路径应该是相同的。如果新旧版本不兼容,Go会在导入路径中包含主版本号以示区分,例如:

import "github.com/sirupsen/logrus/v2"

在实际开发中,如果自动选择的某个依赖版本存在问题,引入了不必要的复杂性,、导致可靠性下降、性能回退等情况,我们可以手动降级至指定版本,可以通过以下命令查看某个依赖包的历史版本:

go list -m -versions github.com/sirusen/logrus

确定要切换的版本后,执行go get命令降级至某个版本:

$go get github.com/sirupsen/logrus@v1.7.0
go: downloading github.com/sirupsen/logrus v1.7.0
go get: downgraded github.com/sirupsen/logrus v1.9.3 => v1.7.0

同样也可以使用go mod tidy命令降级,但是需要先使用go mod edit命令明确指定要依赖的版本:

$go mod edit -require=github.com/sirupsen/logrus@v1.7.0
$go mod tidy       
go: downloading github.com/sirupsen/logrus v1.7.0

添加一个主版本号大于1的依赖

按照语义导入版本规范,如果要为项目引入主版本号大于1的依赖,由于v1v0开头的包版本不兼容,在导入时不能直接使用github.com/user/repo这样的路径,应该使用类似以下的方式的导入路径:

import github.com/user/repo/v2/xxx

并使用go get命令获取对应主版本号下的最新依赖版本或指定版本。

升级依赖版本到不兼容的版本

在升级到一个不兼容的新主版本时,首先需要更新代码中包的导入路径:

import github.com/user/repo/v3/xxx

之后通过go get命令获取最新主版本的依赖包

移除依赖

在需要移除某个依赖时,如果仅删除go文件中的导入语句,构建阶段并不会自动移除该依赖,可以通过go list -m all命令查看依赖列表。

正确的做法是使用go get github.com/user/repo/v8@none,通过@none的方式删除某个依赖,也可以使用go mod tidy命令自动分析依赖情况并清理未使用的依赖项。

兼容vendor

对于一些不方便访问外部网络,并且对Go应用构建性能敏感的环境中,使用vendor机制可以实现与Go module等效的构建过程。

不同于GOPATH模式下的手动维护,Go module提供了快速建立和更新vendor的命令,可以通过以下命令创建vendor目录

go mod vendor

命令执行完成后目录结构如下:

$tree -LF 2 vendor
vendor
├── github.com/
│ ├── google/
│ └── sirupsen/
├── golang.org/
│ └── x/
└── modules.txt

go mod vendor命令在vendor目录下创建了项目所有依赖包的副本,并通过/vendor/modules.txt记录了这些依赖及版本信息。

如果要基于vendor而不是本地缓存的Go module进行构建,可以在go build命令后追加-mod=vendor参数。

自Go1.14版本起,如果项目的顶层目录包含vendor目录,那么go build命令会默认优先基于vendor构建,除非给go build命令传入-mod=mod参数。

替换依赖

使用go mod replace命令或手动编辑go.mod文件均可以实现依赖替换操作。

自定义版本替换

适用于已经发现了公共库存在的未修复漏洞,而库的维护者不接受PR,此时可以fork一份公共库代码,并对其做出必需的改动,改动完成后,利用replace语法将原始依赖路径替换为自定义版本的路径:

// go.mod
require github.com/example/foo v1.0.1
replace github.com/example/foo v1.0.1 => github.com/ourfork/foo v1.0.0

锁定依赖版本

有时我们希望锁定特定版本的依赖,防止因为版本自动更新带来不确定性,也可以使用replace语法锁定依赖版本:

// go.mod
…
require github.com/example/bar v0.3.2
replace github.com/example/bar v0.3.2 => github.com/example/bar v0.3.0

replace github.com/example/bar => github.com/example/bar v0.3.0

替换为本地版本

如果使用的库引入了新特性,但是还未发布正式版本,可能需要使用本地版本进行开发调试或测试,在没有网络或先行开发时非常有用。

可以使用replace行将依赖更改为本地目录:

// go.mod
…
require github.com/example/private v0.3.2
replace github.com/example/private => /path/to/our/local/copy

由于go.mod文件需要提交到代码仓库并与团队内的其它开发者共享,而其中包含的本地路径对其他开发者来说通常是不可用的,这回导致他们无法正常构建项目,对组织内部的持续集成也不友好。

为了解决这一问题,Go在1.18版本中引入了“工作区”(workspace)作为Go module依赖管理的一种补充机制。

  • 0