Go Modules依赖默认存于$GOCACHE/mod而非项目目录或GOPATH,其中$GOCACHE/mod/cache/download存原始zip与校验数据,解压后源码按模块路径+版本哈希组织在$GOCACHE/mod下,go.sum则校验其内容一致性。

Go Modules 的依赖不存于项目目录内,也不在 GOPATH 下,而是统一缓存在 $GOCACHE/mod —— 这是绝大多数人误以为“vendor 就是全部”的根本原因。
go mod download 后的依赖到底放在哪?
执行 go mod download 或首次 go build 时,Go 并不会把依赖复制进你的项目目录(除非你显式 go mod vendor),而是下载并解压到全局缓存路径:$GOCACHE/mod。这个路径默认是:
- Linux/macOS:
$HOME/Library/Caches/go-build(macOS)或$HOME/.cache/go-build(Linux)下的子目录,实际路径由go env GOCACHE确认 - Windows:
%LocalAppData%\go-build
真正存放模块源码的是 $GOCACHE/mod/cache/download(原始 zip 缓存)和 $GOCACHE/mod/cache/download/sumdb(校验数据),而编译时实际读取的是 $GOCACHE/mod 下按模块路径 + 版本哈希组织的 unpacked 目录,例如:
pkg/mod/cache/download/github.com/gin-gonic/gin/@v/v1.9.1.zip pkg/mod/github.com/gin-gonic/gin@v1.9.1/
注意:该路径下每个模块版本对应一个独立目录,且目录名含完整版本号和校验后缀(如 @v1.9.1.h1:abc123...),不是简单拼接。
立即学习“go语言免费学习笔记(深入)”;
go.sum 文件记录什么?它和 $GOCACHE/mod 是什么关系?
go.sum 不是“依赖清单”,而是每个模块版本的**内容哈希快照**,格式为:
github.com/gin-gonic/gin v1.9.1 h1:xxx... github.com/gin-gonic/gin v1.9.1/go.mod h1:yyy...
它的作用是在每次构建时,校验 $GOCACHE/mod 中对应模块的实际内容是否与记录一致。如果有人篡改了本地缓存里的源码,go build 会直接报错:verifying github.com/xxx@v1.2.3: checksum mismatch。
关键点:
-
go.sum不影响下载位置,只影响校验行为 - 删除
go.sum后首次go build会重新生成,但会触发全量重校验(慢) - 若你用
go mod vendor,go.sum仍只校验 vendor 目录外的间接依赖(比如 replace 到本地路径的模块)
为什么 go list -m all 显示的路径和 $GOPATH/src 完全无关?
因为 go list -m all 输出的是模块(module)视角的依赖树,而非包(package)路径。它展示的是 go.mod 中声明的 module path + version,例如:
github.com/sirupsen/logrus v1.9.3 rsc.io/quote v1.5.2
这些路径是模块根目录的 import path,与文件系统物理位置无关。Go 工具链通过 $GOCACHE/mod 中的 unpacked 目录映射到实际代码,而不是去 $GOPATH/src 查找 —— 即使你设置了 GO111MODULE=off,只要当前目录有 go.mod,就走模块模式。
容易踩的坑:
- 误以为
go list -f '{{.Dir}}' <module>返回的是项目内相对路径 —— 实际返回的是$GOCACHE/mod/xxx@v1.2.3这种绝对路径 - 用
find . -name "*.go" | grep logrus找不到任何东西 —— 因为源码不在项目里,在$GOCACHE/mod - 手动修改
$GOCACHE/mod下某模块代码来调试 —— 下次go mod tidy或go get可能覆盖,且破坏go.sum校验
replace 和 exclude 如何影响底层存储结构?
replace 不改变下载行为,只改变 import 解析路径:
- 若
replace github.com/foo/bar => ./local/bar,则编译时所有对github.com/foo/bar的 import 都指向本地./local/bar目录,$GOCACHE/mod中原有版本仍存在,但完全不参与构建 -
exclude则让 Go 工具链彻底忽略某模块版本 —— 它不会出现在go list -m all中,也不会被go mod download拉取,$GOCACHE/mod里也不会有它
真正影响缓存写入的是 go get 和 go mod tidy;replace 是纯逻辑重定向,exclude 是静态过滤。二者都不产生新缓存项,也不删除已有项。
复杂点在于:当你 replace 到一个本地路径,而该路径下又有自己的 go.mod,Go 会递归解析其依赖,并可能将这些依赖也写入 $GOCACHE/mod —— 即使它们未出现在主项目的 go.mod 中。


















