go

Go 系列 包管理工具

Posted by lichao modified on August 14, 2022

包管理工具有 govendor、dep,本指南旨在统一项目间的 go 版本(统一采用性能更优的 1.13) 以及官方推荐的 go mod 进行包管理,历史项目可参考以下步骤进行升级。因考虑到各项目不同的升级周期,推荐用 gvm 进行go不同版本的切换(避免影响项目正常迭代速度)。

为什么要引入依赖?

引入依赖的主要目的是为了复用已有的工作成果。但是直接将其作为依赖加入项目中存在着太多的不确定性:

  • 这个包的 API 会变化
  • 这个包内部行为会变化
  • 这个包的依赖会变化
  • 这个包可能已经已经不存在或无法访问
  • 包与包之间不同依赖的相互冲突 而且随着我们项目的开发,所需要的依赖会越来越懂,手动管理显然不太现实。所以我们需要一个依赖管理工具或者规范来描述和定义包与包之间的依赖关系,并自动化的去处理、解析和满足这些依赖。

从GOPATH/GOROOT谈起

我们首先来看两个重要 PATH:GOROOT 、GOPATH:

  • GOROOT:golang 的安装路径,当安装好 go 之后,默认会安装在 /usr/local/go 之下。GOROOT 的主要作用是标识 go 的当前安装位置。
  • GOPATH:存放 SDK 以外的第三方类库;收藏的可复用的代码,包含三个子目录:

src:存放项目源文件 golang_src

pkg:编译后的包文件 golang_pkg

bin:编译后生成的可执行文件 golang_bin

GOPATH和GOROOT的区别:

简单的说,GOROOT 就是我们 go 的安装目录,仅此而已;而 GOPATH 中存放了除安装包以外的所有代码。

使用 GOPATH 管理外部包: 使用 go get aa/bb/cc 会下载对应的包到 $GOPATH/src/aa/bb/cc,其他项目使用这个包的时候只需要 import aa/bb/cc 即可,也就是说,GOPATH 不管代码是内部还是外部的,总之都在 GOPATH 里,任何 import 包的路径都是从 GOPATH 开始的;唯一的区别就是内部依赖的包是开发者自己写的,外部依赖的包是 go get 下来的。

存在的问题: 当项目依赖的包被修改或者被删除,会影响项目。

  • 之前的解决方案:(管理起来十分混乱且麻烦)
    • 将依赖包拷贝到项目源码树中,然后修改import。
    • 将依赖包拷贝到项目源码树中,然后修改GOPATH。
    • 在某个文件中记录依赖包的版本,然后将GOPATH中的依赖包更新到对应的版本。
    • 用 go get 获取依赖有潜在危险,因为我们不确定最新版依赖是否会破坏掉我们项目对依赖包的使用方式,即当前项目可能会出现不兼容最新依赖包的问题。
  • 后来的解决方案:
    • 引入vendor属性,把项目所依赖的所有包都copy到vendor目录中,让go编译时,优先从项目源码树根目录下的vendor目录查找代码,如果vendor中有,则不再去GOPATH中去查找。

缺陷:

  • vendor目录中依赖包没有版本信息。这样依赖包脱离了版本管理,对于升级、问题追溯,会有点困难。
  • 怎么自动获取本项目依赖了哪些包,并将其拷贝到vendor目录下?
  • vendor目录下面的项目还有vendor怎么办?vendor..vendor..vendor..(俗称套娃)。

因此十分需要一个包管理工具,让我们轻松应对这些问题,常见的工具有godep、govendor、glide、go mod等,我们着重介绍govendor以及go mod


govendor

govendor是一个基于vendor机制实现的Go包依赖管理命令行工具,go语言会把它默认为一个GOPATH。govendor解决了包依赖问题,一个配置文件(vendor.json)全部搞定!

注意:项目必须在$GOPATH/src下。

如何使用

  1. 安装:go get -u -v github.com/kardianos/govendor
  2. 初始化
1
2
  cd xxx
  govendor init

初始化完成后,项目目录中会生成一个vendor文件夹,包含一个vendor.json文件,json文件中包含了项目所依赖的所有包信息。 vendor vendor

  1. 将 GOPATH 中本工程使用到的依赖包自动移动到 vendor 目录中(如果本地 GOPATH 没有依赖包,先 go get 相应的依赖包)

    1
    
    govendor add +e / govendor add +external
    

此时项目已经是 govendor 管理的状态了

常用命令 govendor

状态参数 govendor_param

go mod

包不再保存在GOPATH中,而是被下载到了$GOPATH/pkg/mod路径下。

使用方式

确保 go mod 模式已开启,将 GO111MODULE加入环境变量(1.13开始默认是开启的):

  • GO111MODULE
    • on:支持go mod模式;
    • off:不支持go mod模式;
    • auto (默认模式):如果代码在 gopath 下,则自动使用 gopath 模式;如果代码不在 gopath 下,则自动使用 go mod模式。

流程:

  1. cd 工程目录 projectName
  2. 初始化
  • go mod init projectName 此时会生成 go.mod 文件,go.mod提供了四个命令
    • module 指定包的名字(路径)
    • require 指定的依赖项模块
    • replace 替换依赖项模块
    • exclude 忽略依赖项模块

常用命令 mod

实战演练

go mod init [project]:

创建 go.mod 文件,该文件由 go toolchain 维护,当执行一些命令,如 go get、go build、go mod,go.mod 文件也会有对应的改变。

mod

go run main.go:

mod mod mod

go.sum 是一个构建状态跟踪文件。它会记录当前module所有的顶层和间接依赖,以及这些依赖的校验和,来确保这些模块的将来下载内容与第一次下载的内容相同,但是第一次下载的模块也有可能是非法的(代理服务不可信、模块源被黑等),所以Go 1.13推出GOSUMDB(Go CheckSum Database)用来公证模块的Hash值,从而提供一个可以100%复现的构建过程并对构建对象提供安全性的保证,同时还会保留过去使用的包的版本信息,以便日后可能的版本回退。

间接依赖(indirect) 是指在当前 module 中没有直接 import,而被当前 module 使用的第三方 module 引入的包,相对的顶层依赖就是在当前 module 中被直接 import 的包。如果二者规则发生冲突,那么顶层依赖的规则覆盖间接依赖。

go mod tidy:

非常重要且常用的命令,会使项目引用的包和mod保持同步。

mod mod mod mod

go mod tidy 添加需求时,它会添加模块的最新版本。如果 GOPATH 包括了依赖的旧版本,随后发表一个重大更改,go mod tidy,go build 或 go test 可能会报错。如果发生这种情况,使用 go get(例如 go get github.com/broken/module@v1.1.0)将其降级到较旧的版本,或者使模块与每个依赖项的最新版本兼容。

go mod edit:

go mode edit 提供了一个命令行界面,用于编辑和格式化 go.mod 文件,主要供工具和脚本使用。

1
2
go mod edit --require=github.com/gin-gonic/gin@v1.7.1

mod mod

1
go mod edit -replace example.com/a@v1.0.0=./a

在实际应用中,并不是所有的package都能成功下载,可以通过在 go.mod 文件中使用 replace 指令替换成目标地址的包(一般是GitHub)

注:replace只能管理顶层依赖。

mod

1
go mod edit -dropreplace example.com/a@v1.0.0

mod

使用建议

go mod 命令在 $GOPATH 里默认是执行不了的,因为 GO111MODULE 的默认值是 auto。默认在$GOPATH 里是不会执行, 如果一定要强制执行,就设置环境变量为 on。(从1.13版本默认值已经设置为on)

缺点:

go.mod 中列出了所有的依赖,当时间久项目很大时,如果升级其中一个依赖,有可能导致整个依赖挂掉。

常见问题:

mod

govendor/go mod对比

mod

注意:govendor和go mod最大的区别

  • govendor只是在管理GOPATH目录下的包,也就是说govendor所管理的包都是已经下载到GOPATH的,govendor做的工作只是将项目引用到的外部包从GOPATH拷贝到vendor目录。
  • 那govendor就没有提供安装远端依赖的功能吗?其实是有的。可以使用 govendor fetch [package] (不会在GOPATH备份)
  • 现在govendor也不再提供更新,官方推荐的go mod已经相对比较成熟。

go包管理工具的发展时间线

  • 2012年3月 Go 1 发布,此时没有版本的概念
  • 2013年 Golang 团队在 FAQ 中提议开发者保证相同 import path 的兼容性,后来成为一纸空文
  • 2013年10月 Godep
  • 2014年7月 glide
  • 2014年 有人提出 external packages 的概念,在项目的目录下增加一个 vendor 目录来存放外部的包
  • 2015年8月 Go 1.5 实验性质加入 vendor 机制
  • 2015年 有人提出了采用语义化版本的草案
  • 2016年2月 Go 1.6 vendor 机制 默认开启
  • 2016年5月 Go 团队的 Peter Bourgon 建立委员会,讨论依赖管理工具,也就是后面的 dep
  • 2016年8月 Go 1.7: vendor 目录永远启用
  • 2017年1月 Go 团队发布 Dep,作为准官方试验
  • 2018年8月 Go 1.11发布 Modules 作为官方试验
  • 2019年2月 Go 1.12发布 Modules 默认为 auto
  • 2019年9月 Go 1.13 版本默认开启 Go Mod 模式

参考链接

https://blog.csdn.net/fangkang7/article/details/104764507/

https://studygolang.com/articles/10523

https://www.ctolib.com/topics-123305.html

https://blog.csdn.net/benben_2015/article/details/82227338

https://golang.org/ref/mod#go-mod-download

https://golang.org/ref/mod

https://www.php.cn/faq/417921.html

https://www.jianshu.com/p/760c97ff644c

https://research.swtch.com/vgo-intro

https://www.cyningsun.com/09-07-2019/package-management.html

https://research.swtch.com/vgo