Go的依赖管理曾经是饱受诟病的大问题,随着版本的更迭正逐渐完善起来
现在主要的解决方案有vendor模式和Go Modules 两种管理方式
如果不引入依赖管理单靠GOPATH的话,那么就无法解决项目多版本的问题
1.GOPATH的问题
Go提供了GOPATH路径,Go依赖的所有的第三方库都放在GOPATH这个目录下
但是呢,在GOPATH下每个被依赖到的项目只能保存一个版本的代码
如果存在被依赖的项目存在
版本V1
和版本V2
,当需要用到某个指定版本时,都需要手动去切换,若切换频率高那将是噩梦般存在,严重影响工作效率
2.vendor机制
2.1 什么是vendor
基于vendor机制下,在执行 go build
或 go run
命令时,会按照以下顺序去查找包:
- 当前包下的 vendor 目录
- 向上级目录查找,直到找到 src 下的 vendor 目录
- 在 GOROOT 目录下查找
- 在 GOPATH 下面查找依赖包
2.2 govendor 工具
govendor 是一个基于 vendor 目录机制的包管理工具
支持从项目源码中分析出依赖的包,并从
$GOPATH
复制到项目的vendor
目录下支持包的指定版本,并用
vendor/vendor.json
进行包和版本管理,在json文件可以清晰看到引入的依赖包目录支持用
govendor add/update
命令从$GOPATH
中复制依赖包如果忽略了
vendor
文件,可用govendor sync
恢复依赖包可直接用
govendor fetch
添加或更新依赖包可用
govendor migrate
从其他 vendor 包管理工具中一键迁移到 govendor支持 Linux,macOS,Windows所有操作系统
2.3 安装 govendor
如果 Go 版本为 1.5,则必须手动设置环境变量 set GO15VENDOREXPERIMENT=1(应该没人还用这么低版本的Go的吧~)
- 拉取代码
1 | // 拉取govendor代码并安装 |
- 设置PATH
将 $GOPATH/bin
添加到 PATH 中(大部分在搭建Go开发环境就设置了)
Linux/macOS 如下设置:export PATH="$GOPATH/bin:$PATH"
- 查看命令
在本地的$GOPATH/bin/
下发现 govendor
执行文件说明安装成功了
2.4 初始化并使用
进入Go项目目录下,然后执行
govendor init
初始化项目根目录下即会自动生成
vendor
目录和vendor.json
文件。
此时 vendor.json 文件内容为:
1 | { |
常用命令
将已被引用且在
$GOPATH
下的所有包复制到vendor
目录govendor add +external
从
$GOPATH
中复制指定包到vendor
目录govendor add github.com/golang/protobuf
列出代码中所有被引用到的包及其状态
$ govendor list
列出一个包被哪些包引用
govendor list -v fmt
从远程仓库添加或更新某个包(不会在
$GOPATH
也存一份)govendor fetch golang.org/x/net/context
安装指定版本的包
1 | govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55 |
只格式化项目自身代码(vendor 目录下的不变动)
govendor fmt +local
只构建编译项目内部的包
govendor install +local
只测试项目内部的测试案例
govendor test +local
拉取所有依赖的包到 vendor 目录(包括 $GOPATH 存在或不存在的包)
govendor fetch +out
包已在 vendor 目录,但想从
$GOPATH
更新govendor update +vendor
已修改了
$GOPATH
里的某个包,现在想将已修改且未提交的包更新到vendor
govendor update -uncommitted <updated-package-import-path>
vendor.json
中记录了依赖包信息,该如何拉取更新govendor sync
各子命令详细用法可通过 govendor COMMAND -h 或阅读 github.com/kardianos/govendor/context 查看源码包如何实现的。
2.5 govendor 子命令
命令 | 功能 |
---|---|
init | 创建 vendor 目录和 vendor.json 文件 |
list | 列出过滤依赖包及其状态 |
add | 从 $GOPATH 复制包到项目 vendor 目录 |
update | 从 $GOPATH 更新依赖包到项目 vendor 目录 |
remove | 从 vendor 目录移除依赖的包 |
status | 列出所有缺失、过期和修改过的包 |
fetch | 从远程仓库添加或更新包到项目 vendor 目录(不会存储到 $GOPATH) |
sync | 根据 vendor.json 拉取相匹配的包到 vendor 目录 |
migrate | 从其他基于 vendor 实现的包管理工具中一键迁移 |
get | 与 go get 类似,将包下载到 $GOPATH,再将依赖包复制到 vendor 目录 |
license | 列出所有依赖包的 LICENSE |
shell | 可一次性运行多个 govendor 命令 |
2.6 govendor 状态参数
状态 | 缩写 | 含义 |
---|---|---|
+local | l | 本地包,即项目内部编写的包 |
+external | e | 外部包,即在 GOPATH 中、却不在项目 vendor 目录 |
+vendor | v | 已在 vendor 目录下的包 |
+std | s | 标准库里的包 |
+excluded | x | 明确被排除的外部包 |
+unused | u | 未使用的包,即在 vendor 目录下,但项目中并未引用到 |
+missing | m | 被引用了但却找不到的包 |
+program | p | 主程序包,即可被编译为执行文件的包 |
+outside | 相当于状态为 +external +missing | |
+all | 所有包 |
3.Go Modules
3.1 什么是Go Modules?
Go Modules是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,Go Modules成为了Go语言默认的依赖管理工具。
Go Modules 出现的目的之一就是为了解决 GOPATH 的问题,也可以说准备抛弃 GOPATH
3.2 Go Modules准备工作
升级go版本至少为 Go 1.11,最好是升级到Go 1.13
开启
GO111MODULE
,它有三个可选值:off、on、auto,默认值是auto:GO111MODULE=off:禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。
GO111MODULE=on:启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。
GO111MODULE=auto:当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。
- 设置GOPROXY
Go1.11之后设置GOPROXY命令为:
export GOPROXY=https://goproxy.cn
Go1.13之后GOPROXY默认值为
https://proxy.golang.org
,在国内是无法访问的,推荐修改为goproxy.cn
1 | go env -w GOPROXY=https://goproxy.cn,direct |
创建项目,进入项目根目录下执行:
go mod init
生成 go.mod文件忘记GOPATH的存在,开始去适应Go Modules的工作模式
注意:Go Modules 从远程拉取的包都会存放在 $GOPATH/pkg/mod/下
3.3 go.mod
go.mod文件记录了项目所有的依赖信息,其结构大致如下:
1 | module github.com/littlejoyo/studygo |
module:用来定义包名
require:用来定义依赖包及版本
indirect:表示间接引用
replace:表示依赖包指向本地项目,进行取代作用,在国内访问
golang.org/x
的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。
1 | replace ( |
依赖包会通过go get进行拉取,默认下载最新依赖的包,并且会在go.mod里面记录下版本号
如果有tag,则会拉取最的tag包,比如上面跟在路径后面的
v1.4.0
没有tag则会使用某个固定commit,比如上面的
v0.0.0-20190408063150-3be636683586
当然也可以通过直接指定来获取对应版本的包,上面是默认的拉取规则
3.4 常用go mod命令
下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod download
编辑go.mod文件
go mod edit
1 | go mod edit -fmt # 格式化go.mod文件 |
初始化当前文件夹, 创建go.mod文件
go mod init
增加缺少的module,删除无用的module(常用)
go mod tidy
将依赖复制到vendor下
go mod vendor
校验依赖
go mod verify
解释为什么需要依赖
go mod why
3.5 其他关键词解释
3.5.1 go.sum
go.sum 详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值
主要作用是以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
3.5.2 GOSUMDB
GOSUMDB是一个 Go checksum database的缩写,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改
主要用于保护 Go 不会从任何源头拉到被篡改过的非法 Go 模块版本
GOSUMDB也可以是“off”即禁止 Go 在后续操作中校验模块版本
1 | 格式 1:<SUMDB_NAME>+<PUBLIC_KEY> |
拥有默认值:
sum.golang.org
(之所以没有按照上面的格式是因为 Go 对默认值做了特殊处理)。sum.golang.org
在中国无法访问,故而更加建议将 GOPROXY 设置为goproxy.cn
,因为 goproxy.cn 支持代理 sum.golang.org
3.5.3 GOPRIVATE
一起看下这三个环境变量:GONOPROXY/GONOSUMDB/GOPRIVATE
当GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的时候,这三个环境变量就派上用场了
它们三个的值都是一个以英文逗号 “,” 分割的模块路径前缀,匹配规则同 path.Match。
其中
GOPRIVATE
较为特殊,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以再修改的时候重点关注GOPRIVATE
即可。在使用上来讲,比如
GOPRIVATE=*.corp.example.com
表示所有模块路径以 corp.example.com 的下一级域名 (如 team1.corp.example.com) 为前缀的模块版本都将不经过 Go module proxy 和 Go checksum database
需要注意匹配的时候,是不包括 corp.example.com 本身。
3.5.4 GOPROXY
用于设置 Go 模块代理,其作用是使 Go 在拉取模块版本时,直接通过镜像站点来快速拉取
GOPROXY默认值是
proxy.golang.org
,国内无法访问,所以开启 Go Modules 必需设置镜像代理地址国内常用的镜像代理地址如下:
命令 | 功能 |
---|---|
goproxy.io | 一个全球代理为 Go 模块而生 |
mirrors.aliyun.com/goproxy | 阿里镜像代理 |
goproxy.cn | 七牛云赞助支持 |
3.6 Go module 导入本地包
3.6.1 处于同一项目下
在一个项目(project)下我们是可以定义多个包(package)的。
现在animal/main.go中调用了dog这个包。
1 | animal |
- animal/go.mod 的定义
1 | module animal |
- 如何在
animal/main.go
中导入dog
包?
1 | package main |
3.6.2 不在同一项目
- 目录结构如下:
1 | ├── animal |
因为这两个包不在同一个项目路径下,如果你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。
这个时候我们就需要在
go.mod
文件中使用replace
指令,进行本地包的指向在调用方,也就是animal/go.mod中按如下方式指定使用相对路径来寻找mypackage这个包
1 | module moduledemo |
- 此时,animal包就能找到本地的dog包的代码
微信公众号
扫一扫关注Joyo说公众号,共同学习和研究开发技术。