Go依赖管理 - 解决方案和优化技术

silverwq
2022-07-04 / 0 评论 / 304 阅读 / 正在检测是否收录...

概述

go 的依赖查找和其它语言一样,都是 绝对路径(当前项目目录、vendor目录、gopath、goroot)+ 相对路径(import 关键词后的路径) 进行查找的方式。

查找规则大致如下:

  1. 从当前项目目录开查找依赖包
  2. 从 vendor 目录开查找依赖包 (项目目录必须在 src 目录下)
  3. gopath/pkg/mod 目录查找依赖包(GO111MODULE=on 或者 auto,并且项目目录有 go.mod 文件,项目目录可以在任意位置)
  4. 再从 gopath 目录查找依赖包(必须在项目目录必须在 src 下,并且项目目录下不能有 go·mod 文件)
  5. 最后从 goroot 目录查找依赖包

goPath 模式

该模式下,项目文件必须放在 gopath/src 目录下,并且项目目录下不能有 go·mod 文件,通常会有三个目录:

./bin  存放编译后的二进制
./pkg  存放编译后的库文件
./src  源码文件
└── go_code 因为go get 也会下载到这个目录,所以最好另外启一个目录用于存放自己的代码
    ├── project1
    │   └── main.go
    └── project2

缺点是:

  1. go get 命令 会下载的所有依赖都会放在 src 目录下。这就会导致只能安装最新版本(如果在老项目中执行这个命令,可能会导致依赖不兼容,无法实现多版本管理)
  2. 源代码必须放在 src 下。这就会导致,依赖和源码放在一个目录,很混乱

vendoring 模式

只要版本大于 v1.5,项目目录必须在 src 目录下,就会去 vendor 目录下找。首先会在当前包下的 vendor 目录查找,如果找不到再向上级目录的 vendor 目录下查找,直到找到 src 下的 vendor 目录,如果还是找不到再从 GOPATH 目录找,如果还是找不到,最后从 goroot 目录查找。

通常目录结构如下所示,会有 vendor 目录:

./bin  存放编译后的二进制
./pkg  存放编译后的库文件
./src  源码文件
└── go_code
    ├── project1
    │   └── main.go
    │   └── vendor   第三方包的目录
    └── project2
./vendor             第三方包的目录

优缺点:

  1. 通过复制一份依赖到 vendor 目录来实现项目的多版本
  2. 由于 vendor 目录需要加入版本控制,导致仓库会很大
  3. 源码可以不放在 src 目录下

这期间出现了多种 vendor 依赖包管理工具,都是非官方的:

  1. Godep:解决包依赖的管理工具,Docker、Kubernetes、CoreOS 等 Go 项目都曾用过 godep 来管理其依赖。
  2. Govendor:它的功能比 Godep 多一些,通过 vendor 目录下的 vendor. json 文件来记录依赖包的版本。
  3. Glide:相对完善的包管理工具,通过 glide. yaml 记录依赖信息,通过 glide. lock 追踪每个包的具体修改。

GoModule 模式

当 GO111MODULE=on 或者 auto,并且项目目录有 go.mod 文件,这个时候会去 gopath/pkg 目录下查找依赖包。

GO111MODULE 有三个值:

  1. auto:在 $GOPATH/src 下,且没有包含 go.mod时则关闭 Go Modules,其他情况下都开启 Go Modules。
  2. on:启用 Go Modules
  3. 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=

包和依赖的关系

在使用之前,我们先了解下包和模块的关系,他们像是集合和元素的关系:

  1. Go 包是:同一目录中一起编译的 Go 源文件的集合。在一个源文件中定义的函数、类型、变量和常量,对于同一包中的所有其他源文件可见。
  2. 模块是:存储在文件树中的 Go 包的集合,并且文件树根目录有 go. mod 文件。go. mod 文件定义了模块的名称及其依赖包,每个依赖包都需要指定导入路径和语义化版本(Semantic Versioning),通过导入路径和语义化版本准确地描述一个依赖。
0

评论 (0)

取消