概述
go 的依赖查找和其它语言一样,都是 绝对路径(当前项目目录、vendor目录、gopath、goroot)+ 相对路径(import 关键词后的路径)
进行查找的方式。
查找规则大致如下:
- 从当前项目目录开查找依赖包
- 从 vendor 目录开查找依赖包 (项目目录必须在 src 目录下)
- 从
gopath/pkg/mod
目录查找依赖包(GO111MODULE=on 或者 auto,并且项目目录有go.mod
文件,项目目录可以在任意位置) - 再从 gopath 目录查找依赖包(必须在项目目录必须在 src 下,并且项目目录下不能有
go·mod
文件) - 最后从 goroot 目录查找依赖包
goPath 模式
该模式下,项目文件必须放在 gopath/src 目录下,并且项目目录下不能有 go·mod
文件,通常会有三个目录:
./bin 存放编译后的二进制
./pkg 存放编译后的库文件
./src 源码文件
└── go_code 因为go get 也会下载到这个目录,所以最好另外启一个目录用于存放自己的代码
├── project1
│ └── main.go
└── project2
缺点是:
go get 命令
会下载的所有依赖都会放在 src 目录下。这就会导致只能安装最新版本(如果在老项目中执行这个命令,可能会导致依赖不兼容,无法实现多版本管理)- 源代码必须放在 src 下。这就会导致,依赖和源码放在一个目录,很混乱
vendoring 模式
只要版本大于 v1.5,项目目录必须在 src 目录下,就会去 vendor 目录下找。首先会在当前包下的 vendor 目录查找,如果找不到再向上级目录的 vendor 目录下查找,直到找到 src 下的 vendor 目录,如果还是找不到再从 GOPATH 目录找,如果还是找不到,最后从 goroot 目录查找。
通常目录结构如下所示,会有 vendor 目录:
./bin 存放编译后的二进制
./pkg 存放编译后的库文件
./src 源码文件
└── go_code
├── project1
│ └── main.go
│ └── vendor 第三方包的目录
└── project2
./vendor 第三方包的目录
优缺点:
- 通过复制一份依赖到 vendor 目录来实现项目的多版本
- 由于 vendor 目录需要加入版本控制,导致仓库会很大
- 源码可以不放在 src 目录下
这期间出现了多种 vendor 依赖包管理工具,都是非官方的:
- Godep:解决包依赖的管理工具,Docker、Kubernetes、CoreOS 等 Go 项目都曾用过 godep 来管理其依赖。
- Govendor:它的功能比 Godep 多一些,通过 vendor 目录下的 vendor. json 文件来记录依赖包的版本。
- Glide:相对完善的包管理工具,通过 glide. yaml 记录依赖信息,通过 glide. lock 追踪每个包的具体修改。
GoModule 模式
当 GO111MODULE=on 或者 auto,并且项目目录有 go.mod
文件,这个时候会去 gopath/pkg 目录下查找依赖包。
GO111MODULE 有三个值:
- auto:在 $GOPATH/src 下,且没有包含
go.mod
时则关闭 Go Modules,其他情况下都开启 Go Modules。 - on:启用 Go Modules
- off:关闭 Go Modules,不推荐。
可以使用以下命令修改
go env -w GO111MODULE=on
go mod 文件
go.mod
文件用于声明一个模块,并且指明该模块下所依赖的包,可以使用以下命令生成
# 如果在gopath/src目录下,可以省略包名称,会自动生成
go mod init
# 如果不是在gopath/src目录下的话,需要带有包的名称
go mod init github.com/wuqiyin/helloWorld
mod 文件的大致内容如下所示
module github.com/silver/moduleTest // 用来定义当前项目的模块路径
go 1.17 // 用来设置预期的 Go 版本,目前只是起标识作用。 所以 没有约束、检查 等实际作用。
// 用来设置一个特定的模块版本,格式为<导入包路径> <版本> [// indirect]。
// indirect 代表是间接依赖的意思,如果没有 indirect 的话则是直接依赖包
require github.com/aceld/zinx v1.0.0 // indirect
// 代表的是如果下载zinx v1.0.0包的话,就替换成zinx v1.0.1包
// 可以使用命令:`go mod edit --replace=zinx@v1.0.0=zinx@v1.0.1` 生成
// 要替换的包可以事先下载到pkg目录下
// 这个时候,虽然代码的包就会依赖新的版本
replace zinx v1.0.0 => zinx v1.0.1
// 用来从使用中排除一个特定的模块版本,如果我们知道模块的某个版本有严重的问题,就可以使用 exclude 将该版本排除掉。
exclude ( github.com/google/uuid v1.1.0 )
如果需要修改,可以直接编辑 mod 文件,不过还是建议使用 go mod edit
命令
go mod edit -fmt # go.mod 格式化
go mod edit -require="golang.org/x/text@v0.3.3" # 添加一个依赖
go mod edit -droprequire="golang.org/x/text" # require的反向操作,移除一个依赖
go mod edit -replace="github.com/gin-gonic/gin=/home/colin/gin" # 替换模块版本
go mod edit -dropreplace="github.com/gin-gonic/gin" # replace的反向操作
go mod edit -exclude="golang.org/x/text@v0.3.1" # 排除一个特定的模块版本
go mod edit -dropexclude="golang.org/x/text@v0.3.1" # exclude的反向操作
需要注意的是,当在项目中执行 go get ...
命令的时候,会自动往 mod 文件写入 require 内容。
随着项目的依赖文件的增多,可能很多依赖是冗余的,这个时候需要整理依赖,可以使用以下命令:
go mod tidy
其他相关命令
# 获取所有的指令
go mod help
# 初始化项目。项目名称,一般是和包名称是一样的
go mod init 项目名称
# 添加丢失的模块,并移除无用的模块
# 默认情况下,Go 不会移除 go.mod 文件中的无用依赖。当依赖包不再使用了,可以使用go mod tidy命令来清除它
# go 在运行的时候,好像会自动下载依赖,并不一定要执行这个来下载到本地
go mod tidy
# 下载 go.mod 文件中记录的所有依赖包。而go get是下载单个包。go get暂时还不支持拉取指定分支的指定commit id
# 下载到$GOPATH/pkg/mod目录)
go mod download
# 编辑go.mod文件
go mod edit
# 将所有依赖包存到当前目录下的 vendor 目录下。
go mod vendor
# 检查当前模块的依赖是否已经存储在本地下载的源代码缓存中,以及检查下载后是否有修改
go mod verify
# 查看为什么需要依赖某模块。
go mod why
# 查看现有的依赖结构。
go mod graph
go sum 文件
go.sum
文件主要为了保证一致性,避免包是被篡改过的,构建的时候,会比较 hash 值,大致内容:
# 第一种h1哈希,代表的是对zinx包的所有文件进行hash
# 如果没有哈希值,可能代表包用不上
github.com/aceld/zinx v1.0.0 h1:w1rfb84AsiR/5NRKL8HDBpOB3nJwzwzZyouUjS825q0=
# 第二种.mod h1哈希,代表的是只对对mod文件进行hash
github.com/aceld/zinx v1.0.0/go.mod h1:bMiERrPdR8FzpBOo86nhWWmeHJ1cCaqVvWKCGcDVJ5M=
包和依赖的关系
在使用之前,我们先了解下包和模块的关系,他们像是集合和元素的关系:
- Go 包是:同一目录中一起编译的 Go 源文件的集合。在一个源文件中定义的函数、类型、变量和常量,对于同一包中的所有其他源文件可见。
- 模块是:存储在文件树中的 Go 包的集合,并且文件树根目录有 go. mod 文件。go. mod 文件定义了模块的名称及其依赖包,每个依赖包都需要指定导入路径和语义化版本(Semantic Versioning),通过导入路径和语义化版本准确地描述一个依赖。