Go 学习之依赖管理篇

1|基础环境变量理解

- GOROOT  指向 Go 的安装目录(包含着开发 Go 应用程序所需的所有组件,如编译器、标准库和文档),存储标准库
- GOPATH  指向用户域(用户项目需要位于 src/ 目录中,使每个用户有自己的工作空间,互不打扰),存储第三方库和项目私有库

2|依赖管理工具的产生

2.1|依赖的查找

  • 某个 package 需要引用其他包的时候,编译器会一次从 GOROOT/src/ 和 GOPATH/src 中查找,在 GOROOT 中找到后,便不再去 GOPATH 中查找

2.2|GOPATH 的缺点

  • 无法保存第三方库的多个版本
    • 比如 项目 A 需要使用 第三方库 T 的 v1.0 版本,项目 B 需要使用 第三方库 T 的 v2.0 版本
    • 编译器从 GOPATH/src 下查找 GOPATH/src/T
    • 因为这两个版本,无法共存在 GOPATH 中,所以项目 A、B 无法共享同一个 GOPATH,给维护带来困难
    • 因此进入了第二阶段 —— vendor 机制 —— 将项目依赖私有化

2.3|vendor

  • 一个项目可以有多个 vendor 目录,分别位于不同的目录级别,但建议最好每个项目只在根目录放置一个 vendor 目录

    • vendor 目录只存放本项目需要的依赖包,相当于把依赖私有化,可以理解为「私有的GOPATH目录」,就是别的项目访问不到
    • 编译时,先在 vendor 目录中找依赖包,找不到时,再去 GOPATH 目录寻找
  • 搜索顺序:编译器会从源码文件所在的目录,逐级向上搜索 vendor 目录,搜索依赖包

    • 比如 从 A/B/C/main.go 的 C 目录中先找,再找 B 目录,再找 A 目录
  • 优点:项目发布时,可以把依赖包一并发布,编译时,不会受到 GOPATH 目录的影响;同时也解决了多项目隔离的问题

  • 缺点:随着项目增大,易发生冲突。例如项目依赖开源包 A 和 B,但在 A 的 vendor 目录中 还有个 B 依赖包,且这两个 B 版本不一致,这时候就会产生冲突

  • 接下来官方推出了依赖管理工具 Go Module 来解决问题

2.4|Go Module

  • 主要解决两个问题
    • 准确地记录项目依赖(项目依赖哪些 package,以及精确的 package 版本)
    • 可重复构建,指项目无论在谁的环境中(同平台)构建,产物都是相同的(这个主要解决 GOPATH 时代问题,每个人的 GOPATH 环境中可能同名依赖包但版本不一,因此构建出的产物会有所不同),这个就是依赖上面的准确记录,因此可以重复构建
  • 开源仓库、module 和 package 的关系
    • 一个仓库包含一个或多个 module(一组 package 的集合,一起被标记版本,即一个 module)
    • 每个 module 包含一个或多个 package
    • 每个 package 包含一个或多个源文件
  • 一个 module 的版本号规则必须遵循语义化规范,版本号必须使用 v(major).(minor).(patch),如 v0.1.0、v1/5/0-rc.1
    • major 不兼容的改动时增加此版本
    • minor 新增特性时增加此版本
    • patch bug 修复时增加此版本
    • 语义化版本规范的好处是:用户通过版本号就能了解版本信息

2.5|Go Module 的使用

go.mod 文件,记录依赖包名及版本信息
go.sum 记录每个依赖包的 hash 信息,避免被篡改

go mod init [module名]  初始化一个 module
go get 下载并解压,总是会获取依赖的最新版本,下载之后会更新 go.mod 文件

go.mod 中的 4 个指令
- module  声明 module 的名称
- require 声明依赖及其版本号
- replace 替换 require 中声明的依赖(必须先require声明),使用另外的依赖及其版本号
- exclude 禁用指定的依赖

-----------  go.mod 示例 ------------
module github.com/dfy/gomodule

go 1.13

1. require
// 当有多个包需要引用时,可以用括号括起来
require (
	github.com/google/uuid v1.1.1
  golang.org/x/text v0.3.2
)

2. replace (替换某个依赖包,必须之前有 require)
// replace 的左部分必须在 require 中出现
// 另外注意!!! replace 指令在当前模块不是 main module 时「会被自动忽略」
// Kubernetes 就是利用此特性来实现「对外隐藏依赖版本」,从而达到「禁止直接引用的目的」
replace (
  github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0
  // 也可以使用本地的包来替换
  golang.org/x/text v0.3.2 => ../text  
)

3. exclude (排除某个依赖包)
// 假设 uuid v1.2.0 版本有 bug,exclude 命令可实现将此版本排除在外,不引用
// 虽然上面并没有使用此版本,但是如果「引用了其他包,且该包恰好引用此版本」,exclude 可实现禁用目的
exclude github.com/google/uuid v1.2.0

4. indirect 标识 (间接依赖)
// 有时候,使用 go mod tidy 整理依赖包时,会看到此标识
require github.com/Rican7/retry v0.1.0 //indirect
- 直接依赖 —— 当前项目中新引入的(go get),记录在 go.mod 文件中
- 间接依赖 —— go.mod 有 //indirect 标识,产生原因如下
  - 子依赖包未启用 Go Module,就是没有 go.mod 文件
  - 子依赖包的 go.mod 缺失部分依赖(比如该子依赖包需要依赖包 A 和 B,但是 go.mod 文件中 只记录了 A ,缺少 B 的记录)
- 如何查找间接依赖的来源?
	- go mod why -m <pkg> 可以查看到某个依赖包时被哪个依赖引入
	- go mod why -m all 可以分析所有依赖的依赖链

5. go mod 版本选择机制
	- 要求
		- 若新旧 package 拥有相同的 import 路径,那么新 package 必须兼容旧的 package
		- 若新的 package 不能兼容旧的 package,那么新的 package 要更换 import 路径
	- Go Module 时代,module 版本号要遵循语义化版本规范,即格式为 v<major>.<minor>.<patch>
		- 规定,若 major 版本号大于 1,要显示标记在 module 名字中
		- 如 Go Module 会把 github.com/my/mod/v2 和 github.com/my/mod 视为两个包

6. incompatibel 标识(不兼容)
	github.com/blang/semver v3.5.0+incompatible
	- 表示 module 名字未遵循 Go 推荐风格,就是 module 名字中未携带版本信息,也就是不规范的 module
	- go mod tidy 整理时,会自动寻找最新版本,并加以标记,同时标识上 incompatible

7. 伪版本
	go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
	- 为什么需要伪版本?
		- 有时候发布一个新版本,之后又修复个 bug,但新的语义版本号,但我们要使用新版本,这就需要利用最新的 commit ID
	- 伪版本版本号的格式 vx.y.z-yyyymmddhhhmmss-abcdefabcdef
		- vx.y.z 为真实的语义版本
		- yyyymmddhhhmmss 为该 commit 的提交时间
		- abcdefabcdef 为 commit ID 的前 12

3|依赖包的存储

  • GOPATH 模式下,依赖包存储在 $GOPATH/src 下,该目录下只保存特定依赖包的一个版本

  • GOMODULE 模式下,依赖包存储在 $GOPATH/pkg/mod 下,该目录下可以存储特定依赖包的多个版本

    • $GOPATH/pkg/mod 还有个 cache 目录,用来存储依赖包的缓存,每次下载新依赖包都会在此缓存一份
  • 较于 GOPATH 模式,GOMODULE 有两处不同点

    • 依赖包的目录中包含了版本号,每个版本占用一个目录

    • 依赖包的特定版本目录中只包含依赖包文件,不包含 .git 目录

    • 优点

      • 由于依赖包每个版本都有唯一的目录,所以在多目录场景中使用同一依赖包的多版本时才不会发生冲突
      • 由于依赖包的每个版本都有唯一的目录,表示该目录内容不会发生改变,因此没必要存储其位于版本管理系统(Git)中的版本历史信息
      • 只需要下载模块的代码文件,不必克隆整个仓库,大大节省网络带宽和存储资源
    • 示例

      ${GOPATH}/pkg/mod/gihub.com/google
      - uuid@v1.0.0
      - uuid@v1.1.0
      - uuid@v1.1.1
      
  • 包名大小写敏感问题

    • GOMODULE 模式下,存储时会将包名做大小写编码处理,即每个大写字母将变成"!+相应的小写字母"

4|go.sum 文件说明

  • Go 引入 go.mod 文件来标记每个依赖包的版本,来确保一致性构建
格式为: <module><version>[/go.mod] <hash>
例如:
github.com/google/uuid v1.1.1 hash值
github.com/google/uuid v1.1.1/go.mod hash值
  • 正常情况下,每个依赖包在 go.sum 文件中包含两条记录
    • 第一条记录该依赖包版本整体(所有文件)的 hash 值
    • 第二条记录该依赖包版本中 go.mod 文件的 hash 值,若没有 go.mod 文件,那么只有第一条记录
  • 生成过程
    • go get 命令会首先将依赖包下载到本地缓存目录 $GOPATH/pkg/mod/cache/download 中,该依赖包为后缀为 .zip 的压缩包,如 v1.0.0.zip
    • go get 下载完成后会对此 .zip 包做 hash 运算,并将结果存储在后缀为 .ziphash 的文件中,如 v1.0.0.ziphash
    • 若在项目的根目录中执行 go get 命令,那么 go get 会同步更新 go.mod 和 go.sum 文件
  • 验证
    • 在更新 go.sum 之前,为了确保下载的依赖包时安全可靠的(没有被篡改),go 命令在下载完依赖包后悔咨询 GOSUMDB 环境变量所指示的服务器(也就是校验和数据库),以得到一个权威的依赖包版本的 hash 值
      • 若不一致,则 go 命令将拒绝继续执行,也不会更新 go.sum 文件

4|其他环境变量说明

  • export GO111MODULE=off 切换到 GOPATH 模式
  • export GO111MODULE=on 切换到 GOMODULE 模式
  • export GO111MODULE=auto ( 1.13+版本,项目中包含 go.mod 文件,那么就开启 Go Module 模式; 1.13 以下版本,是根据 GOPATH 路径启用,若项目位于 GOPATH 目录中,Go Module 特性将不开启)
  • 冷知识,为什么叫 GO111MODULE?
    • 因为在 Go 1.11 版本引入的
  • GOPROXY 用于配置代理,因为官方的获取较慢,所以代理帮忙获取,我们再从代理获取
    • 例如 export GOPROXY=“https://goproxy.io,direct”
    • direct 标识的作用是:在代理无法工作是,使用 go 命令仍可以从源版本控制系统中获取模块
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值