GO 包管理机制总结
发展
GO 的包管理机制主要有三个阶段:
- GOPATH 模式
多个工程共享 GOPATH 目录,所有工程的公共依赖包都放在 GOPATH 对应的目录下,产生一个主要问题——随着项目的增加和依赖包越来越多,GOPATH 目录越来越臃肿,里面包含了不同项目的依赖包。当需要把项目放到别的机器上时,依赖包的混乱难以有效迁移。
- go vendor 模式
每个工程可以将依赖包直接放到工程子目录 vendor 中,这样不同的工程可以存放自己需要的各种依赖包到 vendor 目录中,互不影响,当工程放到其它机器上进行开发、编译时,也不在需要在花时间下载所有需要的依赖了。vendor 目录解决了工程依赖打包的问题,可将依赖与工程一起打包,减少下载依赖的时间,它同时被 “GOPATH 模式” 和 “Go Mod 模式” 支持。
go vendor 能够很好地解决 GOPATH 不同工程包混乱的问题,但是没有依赖包的版本记录,可能会造成不同机器之间依赖包版本不一致的问题,需要手动同步依赖包版本。
- go mod 模式
go mod 一方面解决了 GOPATH 下必须将工程放在 GOPATH 目录下的问题,另一方面解决了 go vendor 下手动导包和没有记录依赖包版本变更的问题。
在 go.mod 文件中记录了不同版本依赖包的信息,支持自动下载指定的依赖包,可以自动扫描工程依赖的包信息加入 go.mod。在 go get 依赖包后也会自动加入 go.mod ,同时 go mod 也兼容了 vendor 目录,可以使用 go mod vendor 命令自动将依赖包放在工程子目录的 vendor 目录下。go mod 对 vendor 目录的支持可以很方便的将老的 GOPATH 模式的工程转移到新的包管理工具 go mod 中。
应用
GOPATH/go vendor 模式应用
对于 golang 的工程,若没有开启 go mod,则工程必须放在 GOPATH/src 目录下,工程本身也将作为一个依赖包,可以被其它 GOPATH/src 目录下的工程引用。在有了 go vendor 模式后,vendor 目录下的文件也可以作为第三方的依赖包。当 vendor 目录和 GOPATH 目录均存在依赖包时,是如何进行选择的?
- 在当前目录下的 vendor 目录查找依赖的 package
- 当前目录不存在 vendor 目录,则去上一级目录寻找
- 重复步骤 2 直到进入 $GOPATH/src 目录
- 没有在 vendor 目录中查找到依赖包,则进入 $GOROOT 目录查找依赖包
- $GOROOT 目录也没有依赖包,则进入 $GOPATH 目录寻找依赖包
Go Mod 模式应用
go mod 在 1.11 版本中试验性加入 go ,在 1.13 版本后正式作为官方的包管理工具。
环境变量 GO111MODULE
go 通过环境变量 GO111MODULE 的 3 个值 off、on、auto 来决定是否使用 go mod. 这三个值影响分别如下:
- GO111MODULE=off:关闭 go modules 功能,在编译的时候仍旧在 $GOPATH/src 或者 vendor 目录中寻找依赖。这种包管理模式为 “GOPATH 模式”。
- GO111MODULE=on:开启 go modules 功能,在编译时不会在 $GOPATH/src 中寻找依赖。将在项目根目录生成 go.mod 文件。同时,依赖包不再存放在 $GOPATH/src 目录,而是存放在 $GOPATH/pkg/mod 目录,多个项目可以共享缓存的 modules。
- GO111MODULE=auto:默认值,在 go v1.13 及之后版本中,如果工程目录下包含 go.mod 文件或者位于包含 go.mod 文件的目录下,则开启 go modules 功能。在 go v1.11 中 auto 值需要工程在 GOPATH/src 之外的目录中才会开启 go mod,以确保兼容性。
go mod 命令行
// 常用命令
go mod init // 初始化 go.mod,将开启 mod 使用
go mod tidy // 添加或者删除 modules,取决于依赖的引用
go mod vendor // 复制依赖到 vendor 目录下
// 其它命令
go mod download // 下载 module 到本地
go mod edit // 编辑 go.mod
go mod graph // 打印 modules 依赖图
go mod verify // 验证依赖
go mod why // 解释依赖使用
一般来说,并不需要手动修改 go.mod 文件,通过在工程里使用 go get 命令、go mod tidy 命令,都将会把依赖自动记录到 go.mod 文件中.
go mod tidy // 添加工程中使用到的 go 依赖包,并删除未使用过得依赖包.
go get github.com/repo/package@branch // 下载指定版本的 go 依赖包
go mod 和 go vendor 的区别
go vendor | go mod | |
---|---|---|
项目是否需要放在 $GOPATH/src | 需要 | 非必要(可放可不放) |
依赖的路径 | vendor | GOPATH/pkg/mod |
包的查找顺序 | vendor->GOPATH/src->GOROOT/src | GOPATH/pkg/mod->GOROOT/src |
依赖包的加入方式 | 手动加入,且没有依赖包版本记录。升级更新需要手动操作 | 在 go.mod 文件中记录了不同版本依赖包的信息,支持自动下载指定依赖包,可以自动扫描工程依赖的包信息加入 go.mod |
实践
业务遇到问题
之前的项目使用 go vendor 模式,自己使用的 go 版本为 1.17,首先将 GO111MODULE 设为 off,然后将项目放入系统环境变量 GOPATH/src 下。goland 导入后报错,vscode 导入后也报错。后来发现有的包依赖另外的包,没有放到 vendor 目录下,后来将缺失的包放入 GOPATH/src 下(没有放在 vendor 目录下是为了不影响 git 记录,当然也可以通过 .gitignore 处理)。
vendor 模式下的项目导入流程:
- 将项目放入 $GOPATH/src 下
- 确保所有依赖包都在 vendor 目录下或者在 GOPATH/src 下或者在 $GOROOT/src 下
goland IDE 配置
- 配置好 GOPATH(可采用全局默认 GOPATH)
- 禁用 Go Modules
go mod 实践
# 1. 开启 GO111MODULE
export GO111MODULE=on
# 2. 创建项目目录
mkdir hello
# 3. 创建 go.mod 文件
go mod init example.com/hello
# 4. 创建项目文件(以 hello.go 为例)
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println("go mod test start...")
defer fmt.Println("go mod test end!")
fmt.Println(quote.Hello())
}
# 5. 下载依赖包
go get rsc.io/quote
# 6. go run 运行自动更新 go.mod 和 go.sum
go run hello.go
# 查看 go.mod 和 go.sum 可以看到内容已经更新
参考链接