引言:该系列笔记,是本人自学Go过程中整理的笔记,供大家参考。
Go依赖管理的三个阶段
早期GOPATH
GOPATH是一个环境变量,用来表明你写的Go项目的存放路径,GOPATH路径最好只设置一个,所有源代码都放在GOPATH的src目录下。
在GOPATH目录下新创建三个文件:
- bin:用来存放编译后生成的可执行文件
- pkg:用来存放编译后生成的归档文件(.a结尾)
- src:用来存放项目的源代码文件
代码总是会保存在GOPATH/src目录下,在执行go build、go install或go get等指令后,会将下载的第三方源代码文件放在GOPATH/src目录下, 编译生成的可执行文件放在GOPATH/bin目录下,生成的归档文件放在GOPATH/pkg目录下。
这里会有一个问题就是对于项目1和项目2而言,他们依赖于某个package的不同版本(package v1 和 package v2),但是在src目录下只允许出现一套源代码。这就会导致项目1或项目2编译不通过。这就导致在GOPATH管理模式下,如果有多个项目依赖于同一个库,在src目录下只有一套源代码,无法做到不同项目依赖该库的不同版本。
中期 Go Vendor
- GOPATH环境变量指定文件夹路径
- 手动创建 src/、pkg/、bin/目录
- 新增vendor文件
在GOPATH时期,新增一个vendor文件。其本质是一个目录,所在的该项目使用依赖包会以副本形式放在vendor目录下。这个时候,导入包就会引入优先级:
- 当前项目存在vendor目录,会优先使用该目录下的依赖
- 如果依赖不存在,就会从GOPATH中寻找
Vendor无法很好解决依赖包版本变动问题和一个项目依赖同一个包的不同版本的问题。
项目A
| \
项目B 项目C
| |
项目D-v1 项目D-v2
项目A底层依赖了同一个项目D的不同版本,一旦更新,又该如何指定不同的版本就成了问题。
后期 Go Module
Go module自1.11版本引入,后自1.16版本后默认开启。实现依赖管理的三个要素:
- 配置文件go.mod用于描述依赖
- 代理proxy实现中心仓库管理依赖库
- go mod操作命令用于管理依赖库初始化、更新
使用go module,需要关注环境变量 GO111MODULE,有三个值:on、off、auto。
- off,沿用旧版本的依赖管理方式,在vendor目录或$GOPATH/src目录下找
- on,开启,项目的依赖记录在go.mod中
- auto,go指令根据当前目录来决定是否启动module功能,当项目处在GOPATH/src目录下,则会使用GOPATH/src的依赖包;当项目处在$GOPATH/src目录外,则会使用go.mod中的require声明的依赖包
如何使用go mod
- 在$GOPATH/src目录之外创建项目
- 使用go mod init 项目名(路径),初始化
- 在项目源代码文件中导入第三方包
- 运行程序时,会自动下载依赖包并更新go.mod文件
- 检查go.mod文件中自动更新的依赖清单
- 关键字:module(指定模块的名字,代表路径)、require(指定的依赖模块)、replace(可以替换依赖项模块)、exclude(可以忽略依赖项模块)
go mod 子命令:
go mod graph 以文本模式打印模块需求图
go mod tidy 删除错误或不需要的依赖
go mod init <projectname> 初始化一个module
go mod edit 编辑go.mod文件
go mod vendor 生成Vendor目录
go mod why 查找依赖
go clean -modcache 清理modules缓存
go mod download 下载项目依赖的modules到本地cache (在\$GOPATH/pkg/mod、\$GOPATH/pkg/sumdb下)
如何导入包
基于上述的go mod,我们在创建好项目后,初始化项目(go mod init ),然后我们直接通过 import "/module_path"导入,对于包级声明的变量,不管在不在导入包的同一个源文件下,都可以直接使用。
包的初始化
包的初始化首先是解决包级变量的依赖顺序,原则是自下而上。
var a = b + c // 3. 最后初始化a
var b = f() // 2. 再初始化b
var c = 1 // 1. 先初始化c
func f() int {
return c + 1
}
对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数。
func init() { /* ... */ }