Golang 包依赖管理

为什么需要依赖管理

当下在做一个项目时,我们不可能在从零开始去实现所有的能力,过程中肯定会需要依赖一些现有的第三方库,另外,也会在不同的项目中去复用相同的代码,这些项目本身之外的都属于依赖。

npmpackage.jsonpiprequirement.txt
go mod

基于 GOPATH 的包管理

go modGOPATH

什么是 GOPATH

GOPATHGOPATHsrcgo getsrc
GOPATH							 // 通过环境变量指定的目录
├── bin              // 编译生成的二进制文件
├── pkg              // 预编译文件,以加快程序的后续编译速度
├── src              // 所有的源代码
    ├── github.com
    ├ ...

GOPATH 包管理

GOPATH
go get

govendor 包管理

启用方式:export GO15VENDOREXPERIMENT=1

1.5 版本时,推出了 vendor 机制,就是每个项目的根目录可以有一个 vendor 目录,用来保存项目所依赖的包。当执行 go build 时,会优先从 vendor 目录查找依赖,如果没有则会再到 GOPATH 目录查找。 使用 govendor 方式,需要每个项目单独维护一份对应版本包的副本,但是仍然无法很好的控制依赖包的版本问题。

dep 包管理

通过网上的资料可以看到,Golang 官方曾经推出过实验性质的包管理工具 dep,但是细节有些不太清楚,感兴趣的可以去网上在查阅一下相关资料

go mod 包管理

go mod 是 Golang 1.11 版本引入的官方包依赖管理工具,用于解决没有记录依赖包具体版本的问题,更方便依赖包的管理。

从 Golang 1.13 版本开始,go mod 成为默认的包依赖管理工具,可以在生产环境使用

go mod 特性

相比之前的包管理,go mod 具有一下特性:

GOPATH$GOPATH/pkg/modpkgName + versiongo.modgo.sum

go mod 环境

GO111MODULE
  • off:使用 GOPATH 和 govendor 机制的包管理
  • on:启动 go mod 模式
  • auto:在 GOPATH/src 之外,将自动使用 go mode 模式,否则还是使用之前的模式

go mod 常用命令

go mod init**go mod tidy**go.modgo mod vendorvendorgo build -mod=vendorgo mod download$GOPATH/pkg/mod/cachego mod verifygo mod whygo mod graph

go.mod 文件

go.mod 有以下几个关键词:

  • go:标识当前模块的 Golang 语言版本
  • module:指定包的名字
  • require:指定依赖包
  • replace:替换 require 中声明的依赖包
  • exclude:忽略依赖包的版本

replace 的使用

替换 require 中的包

将 require 中的依赖包替换为指定的包,下面项目最终使用的 uuid 包由 v1.1.1 更换为了 v1.1.0。

require (
    github.com/google/uuid v1.1.1
    ...
)
replace github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0

替换无法下载的包

类似上面代码提到的,也可以整体替换整个包的镜像(host + package)

replace golang.org/x/text => github.com/golang/text latest

调试本地依赖包

replace (
    github.com/google/uuid v1.1.1 => ../uuid
)replace (
  k8s.io/api => ./staging/src/k8s.io/api
  k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver
  k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery
    ...
)

禁止被依赖

发布包后不想被其他项目依赖,如开源的 k8s,它的 go.mod 中 require 中的依赖有大量为 v0.0.0,由于这些版本都不存在,所以其他想要依赖 k8s.io/kubernetes 的项目就无法使用。 其内部是采用 replace 来获取真实的依赖包

module k8s.io/kubernetes

require (
  ...
  k8s.io/api v0.0.0
  k8s.io/apiextensions-apiserver v0.0.0
  k8s.io/apimachinery v0.0.0
  ...
)

// 通过 replace 替换为真实的依赖包
replace (
  k8s.io/api => ./staging/src/k8s.io/api
  k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver
  k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery
    ...
)

go.sum 文件

通过 go.mod 文件信息可以看出(只有 name + version),无法保证构建的一致性,下载的依赖包有可能被恶意篡改。所以引入 go.sum 文件,来记录每个依赖包的哈希值(SHA-256),在 build 时,如果安装的依赖和 go.sum 记录的不一致,则会中断。 一般情况下,对于每个依赖包在 go.sum 文件中会保存两条记录:

  1. 该依赖包所有文件的哈希值
  2. 该依赖包 go.mod 的哈希值(如果不存在,则没有该记录)

在更新 go.sum 之前,为了确保下载的依赖包是真实可靠的,go 命令在下载完依赖包后还会查询 GOSUMDB 环境变量所指示的服务器,以得到一个权威的依赖包版本哈希值。如果 go 命令计算出的依赖包版本哈希值与 GOSUMDB 服务器给出的哈希值不一致,go 命令将拒绝向下执行,也不会更新 go.sum 文件。

Reference