登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go vendor 模式为什么忽略模块缓存中的新版本

来源:17golang原创

时间:2026-10-26 20:03:26 231浏览 收藏

遇到过这种情况:我先用 go get 把某个依赖升级了,模块缓存里已经有新版本,但随后执行构建,程序表现仍像是在使用旧代码。原因通常不是缓存没有更新,而是当前构建已经由 vendor 接管了取包位置。Go 的 vendor 模式会把构建输入固定在主模块根目录的 vendor 树中,模块缓存里的同名版本不会自动覆盖它。

先分清“模块图选了哪个版本”和“构建从哪棵目录取源码”:更新模块缓存不等于更新 vendor,真正让 vendor 变化的是重新执行 go mod vendor。

官方资料:https://go.dev/ref/mod#vendoring

为什么新版本已经在缓存里,构建仍然读旧副本

模块缓存是 Go 下载模块后的存放位置;vendor 是项目根目录下的一份可提交源码树。只要 go.mod 的 go 指令为 1.14 或更高,并且根目录存在与模块文件一致的 vendor/modules.txt,构建命令默认就相当于使用了 -mod=vendor。

在这个模式下,go build、go test 等构建命令从 vendor 读取包,不访问网络,也不从本地模块缓存取包。于是下面两件事可以同时成立:

  • go get example.com/widget@latest 更新了 go.mod 或模块缓存。
  • 项目的 vendor/example.com/widget 仍然是旧版本,构建继续使用它,或因清单不一致直接报错。
Go vendor 构建中 go.mod、vendor 目录、modules.txt 与模块缓存的静态关系图
图1:结构说明图,展示 vendor 构建的源码边界;vendor 是构建包的直接来源,模块缓存处在被忽略的替代来源一侧,不是运行截图。

go.mod、vendor 和 modules.txt 分别负责什么

排查时我会把三个文件系统对象分开看。go.mod 描述主模块的依赖要求和 Go 语义版本;vendor/ 保存实际要交给构建命令的依赖源码;vendor/modules.txt 记录这些源码来自哪些模块版本,Go 会用它检查 vendor 是否与 go.mod 一致。

因此,直接编辑 vendor 里的某个文件,或者只清理模块缓存,都不会完成一次可靠的依赖升级。更稳妥的关系是:先让模块图落到目标版本,再让 go mod vendor 按当前模块图重建 vendor。

# 先把目标版本写入主模块,再按 go.mod 重建 vendor 清单和源码树
go get example.com/widget@v1.8.0
go mod vendor

# 构建时明确使用项目根目录的 vendor,便于在脚本里表达意图
go build -mod=vendor ./...

go mod vendor 会重新构造 vendor 目录,并生成新的 vendor/modules.txt。官方文档也明确说明:如果 go.mod 在清单生成后发生变化,应再次运行这个命令;不要把手工改 vendor 当作版本管理方案。

怎样判断当前命令到底使用了哪种来源

我通常先看项目根目录有没有 vendor 以及清单,再用命令行把“更新依赖”和“构建取源”拆开。go get 的职责是修改主模块依赖;go build -mod=vendor 的职责是从 vendor 构建;go build -mod=mod 则明确要求忽略 vendor,按模块模式解析和访问模块缓存。

# 查看主模块声明的 Go 版本和当前模块图中的版本信息
go env GOMOD GOPATH
go list -m all

# 明确切换构建取源:第一条读 vendor,第二条忽略 vendor
go build -mod=vendor ./...
go build -mod=mod ./...

这里不要把 go list -m all 的版本列表当成“构建一定读取的源码目录”。模块列表描述模块图,而构建命令还要受 -mod 和 vendor 目录影响;模块命令如 go mod download、go mod tidy 仍可访问模块缓存,它们与 build/test 的取源规则不是一回事。

Go 模块更新命令与 vendor 或模块缓存构建取源的关系图
图2:关系说明图,把依赖更新动作与 vendor 或模块缓存构建取源策略分开;看清两组边界后,就不会用 go get 代替 go mod vendor。

需要新版本时,按这张清单处理

  1. 确认目标版本写进了 go.mod:只看到模块缓存目录里有新文件,还不能说明模块图已经选择它。
  2. 重建 vendor:执行 go mod vendor,让源码树和 vendor/modules.txt 同步生成。
  3. 检查构建参数:持续使用 vendor 的项目显式写 -mod=vendor;临时比较模块缓存时使用 -mod=mod。
  4. 不要混用提交策略:如果仓库提交 vendor,就把 go.mod、go.sum、vendor/ 和清单作为同一变更审查;如果不提交 vendor,就统一约定模块模式。
现象优先检查处理
缓存已有新版本,构建仍像旧版本是否存在 vendor 与 modules.txt重新执行 go mod vendor,或用 -mod=mod 做对照
提示 inconsistent vendoringgo.mod 是否在清单生成后变化按当前 go.mod 重建 vendor
想让 CI 完全离线构建vendor 是否已包含全部需要的包固定使用 -mod=vendor 并提交一致的 vendor 树

结语

Go vendor 模式忽略模块缓存中的新版本,是因为两者承担的是不同层次的职责:模块图和缓存可以发生变化,但构建一旦选择 vendor,就只把项目根目录的 vendor 视为依赖源码来源。把目标版本写入 go.mod 后重新运行 go mod vendor,或者明确用 -mod=mod 暂时绕过 vendor,版本行为就会重新变得可解释。

删除模块缓存能让 vendor 自动更新吗?

不能。vendor 是项目目录中的独立源码副本,删除缓存不会改写它;应根据当前 go.mod 重新运行 go mod vendor。

为什么 go list 看到的版本和构建行为不一样?

模块列表描述模块图,而构建命令还要受 -mod 和 vendor 目录影响。需要比较源码来源时,显式分别执行 -mod=vendor 与 -mod=mod。

可以直接改 vendor 里的代码吗?

不建议。它会让源码树偏离 go.mod 和清单记录;如果确实需要修改,应使用模块的 replace 或维护上游版本,再重新生成 vendor。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>