这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
在编写Go代码项目过程中,经常出现报错提示部分包无法被导入,如下所示,因此学习了Go语言依赖管理的原理,希望能给会遇到同样问题的朋友提供参考。
could not import github.com/jinzhu/gorm (no required module provides package "github.com/jinzhu/gorm")
发展历程
Go的依赖管理经历了三个阶段,GOPATH,Go Vendor和Go Module。
GOPATH
通过go env指令可以查看GOPATH路径:
set GO111MODULE=on
...
set GOPATH=C:\Users\hy\go
set GOROOT=D:\Go
...
在包的依赖引用中,系统查找如下路径:
<GOROOT>/src/
<GOPATH>/src/
<GOPATH>/src/<projectName>/vendor/
<GOPATH>/src/<projectName>/src/
Go规定编写自定义GOPATH需要创建文件夹 bin pkg 和src,bin保存项目编译的二进制文件,pkg保存项目编译的中间产物,src保存项目依赖的源代码,在src中可以创建project,并在project中继续创建src。
GOPATH的弊端在于,如果有包的版本升级且前后版本不兼容,可能导致依赖不同版本的项目无法同时构建成功,也即难以实现package的多版本控制。
Go Vendor
对每个项目增加vendor文件夹,将所有依赖包副本放在vendor下,依赖包首先寻址是在vendor,然后再进行查找gopath路径操作。
Go Vendor的弊端在于,项目A依赖的package B 和 package C,B和C依赖同一个包的不同版本,会导致相互兼容存在问题,归根到底vendor不能很清晰的标识依赖的版本概念
Go Module
Go Module 在Go的1.11版本引入,从1.13开始,module模式将是所有开发的默认模式,可以用如下指令查看和修改:go env -w GO111MODULE = on ,go mudule。对于Go语言,依赖管理的三要素为:go.mod用于配置文件,描述依赖;Proxy作为中心仓库管理依赖库,go get/mod是本地工具。
配置文件
go.mod文件组成如图所示:
- 标识模块指示module下的项目路径。
- go的原生库版本
- 单元依赖描述,包括module path +版本号。
gopath和govendor都是源码副本方式依赖,没有版本规则概念,而go.mod为了放方便管理则定义了版本规则,包括语义化版本和基于commit的伪版本。其中语义化版本例如V1.2.3,三个数字中1是major,大版本,不同major可以不兼容;2是minor:新增功能,前后兼容;3是patch。
除了go.mod之外,go命令还维护go.sum的文件,其中包含特定模块版本内容的预期密码散列,go命令使用go.sum文件来确保这些模块的未来下载与第一次下载内容相同,以确保项目所依赖的模块不会意外更改。go.mod和go.sum都应签入版本控制。
go进行版本选择的算法,会选择满足本次构建的最低的兼容版本。
依赖分发
依赖分发主要解决去哪里下载依赖和如何下载的问题。
依赖分发回源:github在go mod的依赖都可以从github中下载。直接使用版本管理下载,无法保证构建稳定,违背了项目托管中心的构建初衷
依赖分发proxy:服务站点proxy,缓存软件内容,版本不会改变,更稳定可靠,可以直接从proxy获取依赖。
依赖分发配置采用goproxy环境变量,参数为服务站点的URL列表,direct表示回源。
常用语句
go mod init创建一个新模块,初始化描述它的go.mod文件。
go build、go test和其他包构建命令根据需要向go.mod添加新的依赖项。
go list-m all列出当前模块及其所有依赖项
go get更改依赖项的所需版本(或添加新的依赖项)。
go mod tidy 删除未使用的依赖项。