Go语言基础:Go Module

146 阅读5分钟

Go程序构建

  1. Go 程序的构建过程就是确定包版本编译包以及将编译后得到的目标文件链接在一起的过程。

  2. Go 语言的构建模式历经了三个迭代和演化过程:

    1. GOPATH
    2. Go 1.5 版本的 Vendor 机制
    3. 现在的 Go Module

GOPATH

  1. Go 编译器可以在本地 GOPATH 环境变量配置的路径下,搜寻 Go 程序依赖的第三方包。如果存在,就使用这个本地包进行编译;如果不存在,就会报编译错误。

  2. go get 命令不仅能将包下载到 GOPATH 环境变量配置的目录下,它还会检查包的依赖包在本地是否存在,如果不存在,go get 也会一并将它们下载到本地。

  3. GOPATH 的缺陷:

    1. 在 GOPATH 构建模式下,Go 编译器实质上并没有关注 Go 项目所依赖的第三方包的版本。go get 下载的包只是那个时刻各个依赖包的最新主线版本,这样会给后续 Go 程序的构建带来一些问题。
    2. 依赖包持续演进,可能会导致不同开发者在不同时间获取和编译同一个 Go 包时,得到不同的结果,也就是不能保证可重现的构建(Reproduceable Build)。
    3. 如果依赖包引入了不兼容代码,程序将无法通过编译。

vendor机制

  1. vendor 机制本质上就是在 Go 项目的某个特定目录下,将项目的所有依赖包缓存起来,这个特定目录名就是 vendor。

  2. 实现可重现的构建,如果使用 vendor 机制管理第三方依赖包,最佳实践就是将 vendor 一并提交到代码仓库中。

  3. 要想开启 vendor 机制,Go 项目必须位于 GOPATH 环境变量配置的某个路径的 src 目录下面。

  4. vendor机制的缺陷:

    1. Go 项目必须放在 GOPATH 环境变量配置的路径下,庞大的 vendor 目录需要提交到代码仓库,不仅占用代码仓库空间,减慢仓库下载和更新的速度,而且还会干扰代码评审,对实施代码统计等开发者效能工具也有比较大影响。
    2. 需要手工管理 vendor 下面的 Go 依赖包,包括项目依赖包的分析、版本的记录、依赖包获取和存放,等等,最让开发者头疼的就是这一点。

Go Module

  1. 一个 Go Module 是一个 Go 包的集合,Go Module 与 go.mod 是一一对应的。

  2. Go Module 构建步骤:

    1. 第一步,通过 go mod init 创建 go.mod 文件,将当前项目变为一个 Go Module;

    2. 第二步,通过 go mod tidy 命令自动更新当前 module 的依赖信息;

      1. go mod tidy 命令会扫描 Go 源码,并自动找出项目依赖的外部 Go Module 以及版本,下载这些依赖并更新本地的 go.mod 文件。
      2. 当将来这里的某个 module 的特定版本被再次下载的时候,go 命令会使用 go.sum 文件中对应的哈希值,和新下载的内容的哈希值进行比对,只有哈希值比对一致才是合法的,这样可以确保项目所依赖的 module 内容,不会被恶意或意外篡改。
    3. 第三步,执行 go build,执行新 module 的构建。

      1. go build 命令会读取 go.mod 中的依赖及版本信息,并在本地 module 缓存路径下找到对应版本的依赖 module,执行编译和链接。

深入 Go Module 构建模式

  1. 语义导入版本 (Semantic Import Versioning):通过在包导入路径中引入主版本号的方式,来区别同一个包的不兼容版本:

    1. 按照语义版本规范,主版本号不同的两个版本是相互不兼容的。
    2. 如果同一个包的新旧版本是兼容的,那么它们的包导入路径应该是相同的。
    3. 将包主版本号引入到包导入路径中,可以像下面这样导入 logrus v2.0.0 版本依赖包。

    image.png

  2. 最小版本选择 (Minimal Version Selection) :Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。换句话说最终选择的是当前依赖包中版本最高的那个,而不是最新的那个包,遵循的原则是刚刚够用就好,不必太新。优势如下:

    1. 对开发者而言,更易于理解和预测,根据依赖图可以很容易确定程序构建最终使用的依赖版本。
    2. 对go核心团队来说,更容易实现,据说实现最小选择的代码也就几十行。
    3. 最小版本选择”为 Go 程序实现持久的和可重现的构建提供了最佳的方案。更容易实现可重现构建。试想一下,如果选择的是最大最新版本,那么针对同一份代码,其依赖包的最新最大版本在不同时刻可能是不同的,那么在不同时刻的构建,产生的最终文件就是不同的。

Go 各版本构建模式机制和切换

image.png

Go Module 操作

  1. 通过go get可以升级或降级某依赖的版本,如果升级或降级前后的版本不兼容,这里千万注意别忘了变化包导入路径中的版本号,这是 Go 语义导入版本机制的要求。
  2. go mod tidy命令,将这个依赖项彻底从 Go Module 构建上下文中清除掉。go mod tidy 会自动分析源码依赖,而且将不再使用的依赖从 go.mod 和 go.sum 中移除。
  3. 如果要基于 vendor 构建go mod vendor,而不是基于本地缓存的 Go Module 构建,需要在 go build 后面加上 -mod=vendor 参数。
  4. 如果 Go 项目的顶层目录下存在 vendor 目录,那么 go build 默认也会优先基于 vendor 构建,除非给 go build 传入 -mod=mod 的参数。