Go 依赖管理 | 青训营笔记

108 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记。

Go 依赖管理

依赖管理

Go 依赖管理演进

Go 的依赖管理经历了三个阶段,分别是 GOPATH -> Go Vendor -> Go Module。

这三个阶段都是围绕一下两个目的来进行的。

  • 不同环境(项目)依赖的版本不同
  • 控制依赖库的版本

GOPATH

GOPATH 是 Go 支持的环境变量,所填的值是 Go 的一个工作区目录。

这个目录下有三个关键的目录:

  • bin:项目编译的二进制文件
  • pkg:项目编译的中间产物
  • src:项目源码

GOPATH 实现依赖管理的逻辑是项目代码直接依赖 src 下的代码,通过 go get 下载最新版本的包到 src 下面。

弊端

无法实现 package 的多版本控制

eg:A 和 B 依赖于某一 package 对不同版本

image-20220510090140505

如果 B 不兼容 A 函数,则 A 和 B 同时构建会失败。

Go Vendor

Go Vendor 相比于 GOPATH,只是在项目目录下多一个 vendor 文件夹,所有依赖包副本形式放在 $Projectroot/vendor 下面。

项目的依赖会优先去找 vendor 文件夹,如果没有再去访问 GOPATH。

Go Vendor 通过每一个项目引入一份依赖的副本,解决了多个项目需要同一个 package 依赖冲突的问题。

弊端
  • 无法控制依赖的版本
  • 更新项目有可能出现依赖冲突,导致编译错误

image-20220510091126905

Go Module

实现了定义版本规则和管理项目依赖关系

  • 通过 go.mod 文件管理依赖包版本
  • 通过 go get / go mod 指令工具管理依赖包

依赖管理三要素

  • 配置文件,描述依赖

    Go 中对应 go.mod

  • 中心仓库管理依赖库

    Go 中对应 Proxy

  • 本地工具

    Go 使用 go get / go mod

依赖配置 - go.mod

在每个包下创建 go.mod 表示这个包是可以被其他人单独的引用的。

go.mod 文件由三部分组成:

  • 模块路径:标识了一个模块可以在哪里找到,可以填 URL,也可以填本地目录

    module example/project/app

  • 原生库:标识依赖的 Go 原生库的一个版本号

    go 1.16

  • 单元依赖

    依赖标识:[Module Path] [Version/Pseudo-version]

    Version 的规范有两种:

    • 语义化版本

      ${MAJOR}.${MINOR}.${PATCH}

      eg: v1.3.0、v2.3.0

    • 基于 commit 伪版本

      ${语义化版本}-${commit的时间戳}-${commit的hash的前缀(12位)}

    后面还可能带有一些关键字。

    • indirect:标识间接依赖

      假设 A -> B -> C,则 A -> B 是直接依赖,A -> C 是间接依赖。

      indirect 用于标识间接依赖,使用时在后面加上 // indirect

    • incompatible:标识可能存在不兼容的代码逻辑

      主版本 2+ 模块会在模块路径增加 /vN 后缀,类似于下面的 lib5

      由于 Go 1.11 才引入 go.mod,对于以前一些仓库可能已经打上了 v2 或更高版本,对于没有 go.mod 文件且主版本 2+ 的依赖,会在后面加上 +incompatible 标识,标识可能存在不兼容的代码逻辑。

image-20220510091737071

依赖配置 - 依赖图

当同一个项目存在间接依赖版本不一时,Go 会选择最低的兼容版本。

image-20220510094141387

如图,X 项目依赖了 A,B,且 A,B分别依赖了 C 的 v1.3, v1.4 两个版本,最终编译时,会使用 C 的 v1.4 版本。

依赖分发 - 回源

在实际开发中,我们把代码托管到类似于 Github 的代码仓库,虽然在 go.mod 中的依赖中可以直接从对应的仓库下载,但存在很多问题。

  • 无法保证构建稳定性

    第三方开发者会增加/修改/删除软件版本

  • 无法保证依赖可用性

    第三方库被删

  • 增加平台的压力

    Github 的初衷是用来托管代码

image-20220510112434512

依赖分发 - Proxy

对于上述问题,可以使用 Proxy 来解决。

Proxy 会缓存源站中的内容,且内容不会改变,即使源站已经删除。

image-20220510112620224

依赖分发 - 变量 GOPROXY

go.mod 通过 GOPROXY 环境变量来控制 Proxy 的配置。

GOPROXY 的值是字符串形式的 URL 列表,用逗号分隔。

//服务站点 URL 列表,direct 表示 源站
GOPROXY = "https://proxy1.cn, https://proxy2.cn, direct"

image-20220510113230669

工具 - go get

使用 go get example.org/pkg 拉取依赖

  • @update 默认是这个,拉取 major 最新版本
  • @none 删除依赖
  • @v1.1.2 tag 版本,语义化版本
  • @23dfdd5 特定的 commit
  • @master 分支的最新 commit

工具 - go mod

  • init 初始化,创建 go.mod 文件
  • download 下载模块到本地缓存
  • tidy 增加需要的依赖,删除不需要的依赖