Go语言中的依赖(包)管理
在以前,Go语言的的包依赖管理一直都被大家所诟病,Go官方也在一直在努力为开发者提供更方便易用的包管理方案,从最初的GOPATH到GO VENDOR,虽然走了不少的弯路,但最终还是拿出了Go Modules这样的解决方案
GOPATH模式
通过配置GOPATH环境变量来设置一个工作目录,该目录中包含所有的源码和编译生成的文件
该目录下有三个主要目录,功能各不相同
bin:存放编译后生成的二进制可执行文件 pkg:存放编译后生成的 .a 文件 src:存放项目的源代码,将自己的代码和下载的包全部放在该目录下进行管理
GOPATH模式的问题
无法在项目中,使用指定版本的包 其他人运行你的程序时,无法保证对方下载的包的版本是你所期望的版本,可能无法正常运行 由于所有项目都共享一个GOPATH,需要导入依赖的时候,都来这里找,一个包只能保留一个版本,无法满足多个项目对不同包版本的需求
GO VENDOR模式
为了解决GOPATH模式包的版本问题,Go 1.5开始支持GO VENDOR模式
在每个项目下都创建一个vendor目录,每个项目所需的依赖都只会下载到自己vendor目录下,项目之间的依赖包互不影响。
在编译时,其搜索包的优先级顺序,由高到低是这样的
当前包下的vendor目录 向上级目录查找,直到找到src下的vendor目录 在GOROOT目录下查找 在GOPATH下面查找依赖包
虽然这个方案解决了一些问题,但是仍然存在一些问题
如果多个项目用到了同一个包的同一个版本,这个包会同时存在于不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理 如果想要分享开源项目,需要将所有的依赖包悉数上传,别人使用的时候,除了项目源码外,还有所有的依赖包全部下载下来,才能保证别人使用的时候,不会因为版本问题导致项目不能如预期那样正常运行
Go Modules模式
为了解决上面的问题,在v1.11时官方加入了Go Modules模式,在v1.14版本中,官方正式推荐该模式,称其已经足够成熟,可以应用于生产上
为了引入Go Modules模式,go env多个个环境变量:GO111MODULE
这里的111,其实就是v1.11版本的象征标志,如当初vendor出现的时候,也多了个GO15VENDOREXPERIMENT环境变量,其中15表示的v1.5版本
GO111MODULE是一个开关,通过它可以开启或关闭go mod模式
它有三个可选值:off、on、auto,默认是auto
off 表示禁用go mod模式,编译时会从GOPATH和vendor文件夹中查找包 on 表示启用go mod模式,编译时会忽略GOPATH和vendor文件夹,只根据go.mod下载依赖 auto 表示当项目根目录有go.mod文件时,自动开启模块支持
go.mod文件
除了go.mod之外,go还维护一个名为go.sum的文件,其中包含特定模块版本内容的预期加密哈希
go命令使用go.sum文件确保这些模块的未来下载检索与第一次下载相同的位,以确保项目所依赖的模块不会出现意外更改,go.sum不需要手工维护,所以可以不用太关注
go.mod文件的内容格式
module语句:指定模块的名称 go语句:指定项目使用的go版本 require语句:指定的依赖项模块 如:require golang.org/x/text v0.3.0 最后的v0.3.0表示指定包的版本 如果不指定版本或者使用latest,则会自动下载最新版版 如果指定某个分支名,会自动下载该分支的最新版本 replace语句:替换依赖模块 如:replace golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0 表示使用后面的包替换前面的包 在程序中我们仍然要写前面的包 exclude语句:忽略依赖模块 如:exclude example.com/banana v1.2.4 表示忽略指定版本的包
go添加了mod命令来配置和操作go mod模式下的包依赖
go mod command 其中可用的command有 download 下载模块到本地缓存,缓存路径是$GOMODCACHE edit 是提供了命令版编辑go.mod的功能,如go mod edit -fmt go.mod会格式化go.mod graph 把模块之间的依赖结构打印出来 init pack_name:初始化模块,生成go.mod文件 tidy 增加缺失的包,移除没用的包 vendor 把依赖拷贝到 vendor/ 目录下 verify 校验模块是否被篡改过 edit命令: go mod edit [-fmt|-print|-json] [go.mod] 最后的go.mod可以省略 -fmt:格式化go.mod文件的内容 -print:打印go.mod文件的原始内容 -json:以json格式打印go.mod文件的内容 go mod edit [editing flags] [go.mod] 最后的go.mod可以省略 editing flags有: -require=path[@version] :添加依赖 -droprequire==path[@version] :删除依赖 -replace=old[@v]=new[@v] :替换依赖 -exclude=path@version :添加忽略的依赖
使用实例
切换到项目的根目录中使用命令go mod init pack_name即可初始化模块
在当前目录创建go.mod文件,之后的包的管理都是通过这个文件维护管理
init初始化时指定的模块名称作为引入自己的包名(用来引入项目根目录下的其他目录)
如/path/study目录为我们的项目根目录,在该目中又有一个sql目录(主要存放数据库操作) /path/study/sql目录中的程序文件声明的包名为mysql 使用命令初始化:cd /path/study & go mod init gostudy 程序文件的内容 package main import ( "fmt" "gostudy/sql" ) func main(){ mysql.Run("select * from info") fmt.Println("执行了sql目录中的Run方法") }
从以上代码我们可以看出来
1、初始化时声明的包名和程序中声明的包无关,也可目录名无关 2、初始化时声明的包仅仅是用来在导入包时作为根目录
如何添加依赖
1、只要在项目中有import,当使用go run、go build或者go test等命令就会自动下载(如果发现本地没有)并把具体的依赖关系和版本写入到go.mod和go.sum文件中 2、使用命令go get -v -u path@version下载后,会自动写入go.mod go get命令的选项 -d :表示仅下载依赖但不安装依赖 -u :表示使用网络更新下载依赖,默认情况下不检测依赖的更新 -fix:表示在解析依赖项或生成代码之前对下载的包运行修复工具 -t :表示同时下载为指定包构建测试所需的包 -f :必须同时使用-u才有效,表示当本地存在时不验证本地包是否是从指定网址下载,如果修改了包的源文件可以使用该选项 -v :启用详细进度和调试输出