构建模式:Go 是怎么解决包依赖管理问题的?
Go 项目的布局标准是什么?
- 首先,对于以生产可执行程序为目的的 Go 项目,它的典型项目结构分为五部分:
- 放在项目顶层的 Go Module 相关文件,包括 go.mod 和 go.sum;
- cmd 目录:存放项目要编译构建的可执行文件所对应的 main 包的源码文件;
- 项目包目录:每个项目下的非 main 包都“平铺”在项目的根目录下,每个目录对应一个 Go 包;
- internal 目录:存放仅项目内部引用的 Go 包,这些包无法被项目之外引用;
- vendor 目录:这是一个可选目录,为了兼容 Go 1.5 引入的 vendor 构建模式而存在的。这个目录下的内容均由 Go 命令自动维护,不需要开发者手工干预。
- 第二,对于以生产可复用库为目的的 Go 项目,它的典型结构则要简单许多,我们可以直接理解为在 Go 可执行程序项目的基础上去掉 cmd 目录和 vendor 目录。
Go 构建模式是怎么演化的?
- Go 程序的构建过程就是确定包版本、编译包以及将编译后得到的目标文件链接在一起的过程。
- Go 语言的构建模式历经了三个迭代和演化过程,分别是最初期的 GOPATH、1.5 版本的 Vendor 机制,以及现在的 Go Module。
- GOPATH
- Go 语言在首次开源时,就内置了一种名为 GOPATH 的构建模式。
- 在这种构建模式下,Go 编译器可以在本地 GOPATH 环境变量配置的路径下,搜寻 Go 程序依赖的第三方包。
- 如果存在,就使用这个本地包进行编译;如果不存在,就会报编译错误。
- 我们可以通过 go get 命令将本地缺失的第三方依赖包下载到本地。
- 在 GOPATH 构建模式下,Go 编译器实质上并没有关注 Go 项目所依赖的第三方包的版本。
- Vendor
- vendor 机制本质上就是在 Go 项目的某个特定目录下,将项目的所有依赖包缓存起来,这个特定目录名就是 vendor。
- Go 编译器会优先感知和使用 vendor 目录下缓存的第三方包版本,而不是 GOPATH 环境变量所配置的路径下的第三方包版本。这样,无论第三方依赖包自己如何变化,无论 GOPATH 环境变量所配置的路径下的第三方包是否存在、版本是什么,都不会影响到 Go 程序的构建。
- 如果你将 vendor 目录和项目源码一样提交到代码仓库,那么其他开发者下载你的项目后,就可以实现可重现的构建。因此,如果使用 vendor 机制管理第三方依赖包,最佳实践就是将 vendor 一并提交到代码仓库中。
- Go Module
- 从 Go 1.11 版本开始,除了 GOPATH 构建模式外,Go 又增加了一种 Go Module 构建模式。
- 一个 Go Module 是一个 Go 包的集合。
- module 是有版本的,所以 module 下的包也就有了版本属性。
- 这个 module 与这些包会组成一个独立的版本单元,它们一起打版本、发布和分发。
- 在 Go Module 模式下,通常一个代码仓库对应一个 Go Module。
- 一个 Go Module 的顶层目录下会放置一个 go.mod 文件,每个 go.mod 文件会定义唯一一个 module,也就是说 Go Module 与 go.mod 是一一对应的。
- go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。
创建一个 Go Module
-
将基于当前项目创建一个 Go Module,通常有如下几个步骤:
- 第一步,通过 go mod init 创建 go.mod 文件,将当前项目变为一个 Go Module;
- 第二步,通过 go mod tidy 命令自动更新当前 module 的依赖信息;
- 第三步,执行 go build,执行新 module 的构建。
Go Module 构建模式
- Go Module 规定:如果同一个包的新旧版本是兼容的,那么它们的包导入路径应该是相同的。
- Go 的“语义导入版本”机制
- 通过在包导入路径中引入主版本号的方式,来区别同一个包的不兼容版本,这样一来我们甚至可以同时依赖一个包的两个不兼容版本。
- Go Module 的最小版本选择原则
- Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。