Go:管理依赖

代码使用外部软件包时,这些以模块形式分发的包便成为依赖。随着时间推移,你可能需要升级或替换它们。Go 提供依赖管理工具,帮助你在引入外部依赖时维护 Go 应用的安全性。

本页介绍代码依赖的管理任务。多数任务可以通过 Go 工具完成,也涵盖一些可能有用的其他依赖相关操作。

相关资料

  • 如果刚开始将依赖作为模块使用,可阅读入门教程。
  • 使用 go 命令管理依赖,有助于保持依赖要求一致,确保 go.mod 内容有效。完整命令说明见 Command go;也可在命令行输入 go help command-name,例如 go help mod tidy。
  • 修改依赖的 Go 命令会编辑 go.mod;文件内容说明见 go.mod 文件参考。
  • 让编辑器或 IDE 识别 Go 模块,可以简化管理工作。支持 Go 的编辑器见编辑器插件与 IDE 资料。
  • 本页不介绍如何开发、发布并为供他人使用的模块设定版本;这部分见“开发与发布模块”。

使用与管理依赖的工作流

可以在 pkg.go.dev 搜索有用的软件包,再通过 Go 工具将其用于自己的代码,调用其中的函数。最常见的步骤是:

  1. 在 pkg.go.dev 找到合适的软件包。
  2. 在代码中导入需要的包。
  3. 如果代码还不属于模块,将其放入模块以跟踪依赖,详见下文“启用依赖跟踪”。
  4. 把外部包添加为依赖,以便管理。
  5. 根据需要升级或降级依赖版本。

以模块形式管理依赖

在 Go 中,依赖以模块形式管理,而模块包含代码导入的软件包。这个过程由以下机制支持:

  • 去中心化的模块发布与代码获取机制:开发者从自己的仓库向他人提供模块,并通过版本号发布。
  • pkg.go.dev 提供的软件包搜索引擎与文档浏览器,详见下文“查找并导入有用的软件包”。
  • 模块版本编号约定,帮助理解模块的稳定性与向后兼容保证。
  • Go 工具,简化获取模块源码、升级等依赖管理操作。

查找并导入有用的软件包

在 pkg.go.dev 搜索提供所需函数的包。找到后,在页面顶部找到包路径,点击 Copy path 将其复制到剪贴板,再粘贴到代码的 import 语句中:

import "rsc.io/quote"

导入包后,需要启用依赖跟踪并获取包源码,以便编译。详见下面两节。

在代码中启用依赖跟踪

要跟踪和管理依赖,先让代码成为一个独立模块。这会在源码树根目录创建 go.mod,后续添加的依赖会列在其中。

进入代码根目录,使用 go mod init:

$ go mod init example/mymodule

命令的参数是模块路径。如果可能,应使用源码仓库地址作为模块路径。

如果一开始还不知道最终仓库地址,可以先用安全的替代名称,例如自己拥有的域名,或其他由你控制的名称(如公司名),再加上对应模块名称或源码目录的路径。详见下文“模块命名”。

使用 Go 工具管理依赖时,工具会更新 go.mod,使它持续反映当前依赖列表。添加依赖时,Go 工具还会创建包含依赖模块校验和的 go.sum。Go 用它验证下载模块文件的完整性,尤其便于协作开发者验证相同依赖。

将 go.mod 和 go.sum 与代码一起提交到仓库。更多信息见 go.mod 参考。

模块命名

运行 go mod init 时指定的模块路径,就是模块名称,也会成为模块内各包导入路径的前缀。应确保它不会与其他模块路径冲突。

模块路径至少需要表示其来源,例如公司、作者或所有者名称;也可以进一步描述模块本身或功能。典型形式如下:

<prefix>/<descriptive-text>

前缀通常是描述模块来源的字符串,可以是:

  • Go 工具能找到源码的仓库地址。发布模块时必须采用这种可访问的路径,例如 github.com/<project-name>/。如果将来可能发布给他人使用,推荐遵循此做法;详见“开发与发布模块”。
  • 由你控制的名称。若不用仓库地址,应选择确信不会被他人使用的前缀,例如公司名称;避免 widgets、utilities、app 等通用词。

描述部分可采用项目名称。请记住,描述具体功能主要是包名称的职责,模块路径则为这些包名称提供命名空间。

保留的模块路径前缀

Go 保证以下字符串不会用于常规包名称:

  • test:用于在本地测试其他模块函数的模块。例如,测试本身可能运行 go mod init test,再按特定方式配置该模块,供 Go 源码分析工具测试。
  • example:用于部分 Go 文档中的示例模块路径,例如教程中创建一个仅用于跟踪依赖的模块。Go 文档也使用 example.com 表示可能会发布的示例模块。

添加依赖

导入已发布模块中的包后,可以使用 go get 把该模块添加为受管理的依赖。此命令会按需:

  • 在 go.mod 中添加构建命令行所列包所需模块的 require 指令。require 跟踪本模块依赖的最低模块版本。
  • 下载模块源码,以便编译依赖这些模块的包。下载来源可以是 proxy.golang.org 等模块代理,也可以是版本控制仓库;源码会缓存在本地。

可以配置 Go 工具从哪里下载模块,详见“指定模块代理服务器”。

要添加当前模块中某个包的全部依赖,运行类似以下命令;. 表示当前目录的包:

$ go get .

要添加特定依赖,将模块路径作为参数:

$ go get example.com/theirmodule

命令还会验证每个下载的模块,确保其自发布以来未被改变。如果发布后内容发生变化,例如开发者更改了对应提交的内容,Go 工具就会报告安全错误。这能防范被篡改的模块。

获取特定依赖版本

在 go get 中指定版本,即可获取依赖模块的特定版本。命令会更新 go.mod 中的 require 指令;也可以手动修改该指令。

以下情况可能需要这样做:试用特定预发布版本;发现当前版本不适用,希望切换到已知可靠版本;或者升级、降级已有依赖。

要获取指定版本,在模块路径后添加 @ 和版本号:

$ go get example.com/theirmodule@v1.3.4

要获取最新版本,添加 @latest:

$ go get example.com/theirmodule@latest

以下 go.mod 示例展示了指定版本号的 require 指令:

require example.com/theirmodule v1.3.4

发现可用更新

使用 go list 可以列出当前模块的依赖,以及各自可用的最新版本。发现升级后,可以结合自己的代码进行验证,再决定是否采用新版本。更多说明见 go list -m。

列出当前模块的全部依赖及各自最新版本:

$ go list -m -u all

显示指定模块的最新可用版本:

$ go list -m -u example.com/theirmodule

升级或降级依赖

先用 Go 工具查找可用版本,再将不同版本添加为依赖。查找版本使用前述 go list,选择特定版本则使用前述 go get。

同步代码中的依赖

可以确保所有被导入的包都有对应依赖,同时移除已经不再导入的包所对应的依赖。代码与依赖经历多次修改后,受管理的依赖、已下载模块,可能与实际导入包需要的集合不再匹配,这时尤其需要整理。

go mod tidy 根据代码中导入的包修改 go.mod:补上必需但缺失的模块,并移除不再提供相关包的未使用模块。原文介绍的命令不需要位置参数;添加 -v 标志可以显示被移除模块的信息:

$ go mod tidy

针对未发布模块进行开发与测试

可以让代码使用尚未发布的依赖模块,其代码可能位于原仓库、仓库的 fork,或与当前模块同一磁盘上。

常见用途包括:fork/克隆外部模块后修改代码、准备修复并向原作者提交拉取请求;或者开发尚未发布、无法通过 go get 从仓库获取的新模块。

使用本地目录中的模块代码

可以指定依赖模块与当前模块位于同一磁盘。这适用于测试自己开发的独立模块,或测试对外部模块所做的修复、功能扩展。外部模块也可以来自自己的仓库 fork,见下一节。

在 go.mod 中使用 replace,替换 require 中的模块路径,即可让 Go 使用本地代码。

以下示例依赖 example.com/theirmodule,使用不存在的版本 v0.0.0-unpublished 确保替换正确生效。replace 将其指向与当前模块目录同级的 ../theirmodule:

module example.com/mymodule

go 1.23.0

require example.com/theirmodule v0.0.0-unpublished

replace example.com/theirmodule v0.0.0-unpublished => ../theirmodule

设置 require/replace 配对时,使用 go mod edit 和 go get 维持依赖要求一致:

$ go mod edit -replace=example.com/theirmodule@v0.0.0-unpublished=../theirmodule
$ go get example.com/theirmodule@v0.0.0-unpublished

版本编号详见“模块版本编号”。Go 1.18 增加了工作区模式,可以同时处理多个模块,详见多模块工作区入门教程。

使用自己 fork 仓库中的外部模块

fork 外部模块以修复问题或添加功能后,可以让 Go 工具使用 fork 中的源码,从自己的代码中测试这些更改。也可以使用同一磁盘中的本地目录,见上一节。

用 replace 把外部模块的原路径替换为自己的 fork 路径。这样编译等操作会使用替换后的源码地址,同时保留代码中原有的 import 路径。指令详情见 go.mod 文件参考。

以下示例把 example.com/theirmodule 替换为 example.com/myfork/theirmodule:

module example.com/mymodule

go 1.23.0

require example.com/theirmodule v1.2.3

replace example.com/theirmodule v1.2.3 => example.com/myfork/theirmodule v1.2.3-fixed

设置配对指令时,使用 Go 命令保持要求一致。先用 go list 获取当前模块正在使用的版本,再用 go mod edit 替换为 fork:

$ go list -m example.com/theirmodule
example.com/theirmodule v1.2.3
$ go mod edit -replace=example.com/theirmodule@v1.2.3=example.com/myfork/theirmodule@v1.2.3-fixed

通过仓库标识获取特定提交

可以用 go get 添加仓库中特定提交的未发布模块代码。在命令中用 @ 指定目标,Go 会在 go.mod 中添加 require,版本号为根据提交信息生成的伪版本。以下示例基于 Git 仓库:

获取特定提交,在路径后添加 @commithash:

$ go get example.com/theirmodule@4cf76c2

获取特定分支,添加 @branchname:

$ go get example.com/theirmodule@bugfixes

移除依赖

代码不再使用模块中的任何包时,可以停止跟踪该模块。要移除全部未使用模块,运行 go mod tidy;它也可能补上构建所需但缺失的依赖:

$ go mod tidy

要移除特定依赖,用 go get 指定模块路径并加上 @none:

$ go get example.com/theirmodule@none

此命令还会降级或移除依赖于被移除模块的其他依赖。

工具依赖

工具依赖用于管理以 Go 编写、在开发当前模块时使用的工具,例如配合 go generate 使用的 stringer,或提交改动前运行的特定检查器、格式化工具。

Go 1.24 及以后可以这样添加工具依赖:

$ go get -tool golang.org/x/tools/cmd/stringer

这会向 go.mod 添加 tool 指令,并确保所需 require 存在。之后,可把工具导入路径中最后一个非主版本号部分传给 go tool 运行工具:

$ go tool stringer

如果多个工具最后一段路径相同,或它与 Go 发行版自带工具重名,必须传入完整包路径:

$ go tool golang.org/x/tools/cmd/stringer

不带参数运行 go tool,查看全部可用工具:

$ go tool

可以手动添加 tool 指令,但必须确保定义该工具的模块具有对应 require。补齐缺失要求的最简单方式是:

$ go mod tidy

工具依赖的要求与模块图中的其他要求相同:参与最小版本选择,并遵循 require、replace 和 exclude 指令。由于模块裁剪,如果你依赖的模块自己具有工具依赖,仅用于满足该工具依赖的要求通常不会成为你的模块要求。

tool 元模式允许同时操作所有工具。例如,go get tool 可升级全部工具,等同于 go get tool@upgrade;go install tool 则把全部工具安装到 $GOBIN。

Go 1.24 之前,可在模块中添加一个通过构建约束排除的 Go 文件,在其中加入空白导入,实现类似效果;然后用完整包路径配合 go run 执行工具。

指定模块代理服务器

默认情况下,Go 工具从 Google 运营的公共模块镜像 proxy.golang.org,或模块仓库直接下载模块。也可以指定其他代理服务器,用于下载和验证模块。

例如,你或团队可能搭建或选择了其他代理,以更好地控制依赖使用方式。

将 GOPROXY 环境变量设为一个或多个服务器 URL。Go 工具按顺序尝试。默认配置先使用 Google 公共代理,再按模块路径从仓库直接下载:

GOPROXY="https://proxy.golang.org,direct"

其他受支持行为见 Go 命令参考中的 GOPROXY 说明。

代理 URL 可以用逗号或竖线分隔。使用逗号时,只有当前服务器返回 HTTP 404 或 410,才尝试下一个 URL:

GOPROXY="https://proxy.example.com,https://proxy2.example.com"

使用竖线时,无论 HTTP 错误码是什么,都会尝试下一个 URL:

GOPROXY="https://proxy.example.com|https://proxy2.example.com"

Go 模块也经常在无法从公共互联网访问的版本控制服务器或模块代理上开发、分发。可以设置 GOPRIVATE,配置 go 命令从私有来源下载并构建模块。

GOPRIVATE 或 GONOPROXY 可以设为通配模式列表,匹配私有且不应向任何代理请求的模块前缀。例如:

GOPRIVATE=*.corp.example.com,*.research.example.com
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容