在使用Golang过程中,有一个非常令人头大的问题: 缺少依赖库版本功能管理, 比如某些依赖在某个commit之后发生了API变更之后,如果不修改代码很难兼容,然而开发者之间很有可能因为参与的时间不同,导致执行go get命令获取的版本不同,而导致各种问题, 甚至是编译不通过。因此需要有一个包依赖的版本控制工具。

vendor之前

在vendor出来之前, 以godep为主比较流行, godep的原理非常简单:

godep把第三包的版本依赖信息记录在Godeps.json下,并且把第三包完整拷贝一份到vendor下面。通过对Godeps.json文件进行版本管理即可以管理整个项目的第三方包依赖信息。

可以看到godep只是把第三方包进行单独到依赖管理,而新增到第三包还是会被get到GOPATH中, 如果多个项目用同一个第三包的不同版本时, 那就完蛋了

vendor的历史


vendor机制就是用来解决第三方包依赖问题:

  • golang 1.5引入, 默认是关闭的, 通过手动设置环境变量:GO15VENDOREXPERIMENT=1开启
  • golang 1.6默认开启
  • goalng 1.7 vendor作为功能支持,取消GO15VENDOREXPERIMENT环境变量

vendor的原理很简单: 将第三方依赖放入当前项目vendor目录中, 编译的时候从vendor目录中查找依赖而不从GOPATH/src中对应目录中查找。

新增的第三方包直接被get到根目录的vendor文件夹下,不会与其它的项目混用第三方包,完美避免多个项目同用同一个第三方包的不同版本问题。

因此只需要对vendor/vendor.json进行版本控制,即可对第三包依赖关系进行控制。

vendor的使用

安装govendor
go get -u -v github.com/kardianos/govendor
创建一个golang的项目

比如我创建一个简单的依赖ssh服务的包

package main
import (
 "bytes"
 "fmt"
 "log"
 "golang.org/x/crypto/ssh"
)
func main() {
 ce := func(err error, msg string) {
  if err != nil {
   log.Fatalf("%s error: %v", msg, err)
  }
 }
 client, err := ssh.Dial("tcp", "localhost:1234", &ssh.ClientConfig{
  User: "root",
  Auth: []ssh.AuthMethod{ssh.Password("xxx")},
 })
 ce(err, "dial")
 session, err := client.NewSession()
 ce(err, "new session")
 defer session.Close()
 modes := ssh.TerminalModes{
  ssh.ECHO:          1,
  ssh.ECHOCTL:       0,
  ssh.TTY_OP_ISPEED: 14400,
  ssh.TTY_OP_OSPEED: 14400,
 }
 err = session.RequestPty("xterm-256color", 80, 40, modes)
 ce(err, "request pty")
 if err := session.Setenv("LC_USR_DIR", "/usr"); err != nil {
  panic("Failed to run: " + err.Error())
 }
 var b bytes.Buffer
 session.Stdout, session.Stderr = &b, &b
 if err := session.Run("ls -l $LC_USR_DIR"); err != nil {
  panic("Failed to run: " + err.Error())
 }
 fmt.Println(b.String())
初始化vendor文件
➜  govendor_test govendor init
➜  govendor_test cat vendor/vendor.json
{
 "comment": "",
 "ignore": "test",
 "package": [],
 "rootPath": "govendor_test"
}
➜  govendor_test tree .
.
├── main.go
└── vendor
    └── vendor.json
 

初始化完成后会生成一个vendor的文件夹, 因为我还没添加依赖, 所以vendor.json里面并没有相关依赖包的描述

添加依赖的第三方包
 ➜  govendor_test govendor add +external
➜  govendor_test cat vendor/vendor.json
{
 "comment": "",
 "ignore": "test",
 "package": [
  {
   "checksumSHA1": "C1KKOxFoW7/W/NFNpiXK+boguNo=",
   "path": "golang.org/x/crypto/curve25519",
   "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
   "revisionTime": "2017-02-08T20:51:15Z"
  },
  {
   "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=",
   "path": "golang.org/x/crypto/ed25519",
   "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
   "revisionTime": "2017-02-08T20:51:15Z"
  },
  {
   "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
   "path": "golang.org/x/crypto/ed25519/internal/edwards25519",
   "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
   "revisionTime": "2017-02-08T20:51:15Z"
  },
  {
   "checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=",
   "path": "golang.org/x/crypto/ssh",
   "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
   "revisionTime": "2017-02-08T20:51:15Z"
  }
 ],
 "rootPath": "govendor_test"
}
➜  govendor_test tree .
.
├── main.go
└── vendor
    ├── golang.org
    │   └── x
    │       └── crypto
    │           ├── LICENSE
    │           ├── PATENTS
    │           ├── curve25519
    │           │   ├── const_amd64.h
    │           │   ├── const_amd64.s
    │           │   ├── cswap_amd64.s
    │           │   ├── curve25519.go
    │           │   ├── doc.go
    │           │   ├── freeze_amd64.s
    │           │   ├── ladderstep_amd64.s
    │           │   ├── mont25519_amd64.go
    │           │   ├── mul_amd64.s
    │           │   └── square_amd64.s
    │           ├── ed25519
    │           │   ├── ed25519.go
    │           │   └── internal
    │           │       └── edwards25519
    │           │           ├── const.go
    │           │           └── edwards25519.go
    │           └── ssh
    │               ├── buffer.go
    │               ├── certs.go
    │               ├── channel.go
    │               ├── cipher.go
    │               ├── client.go
    │               ├── client_auth.go
    │               ├── common.go
    │               ├── connection.go
    │               ├── doc.go
    │               ├── handshake.go
    │               ├── kex.go
    │               ├── keys.go
    │               ├── mac.go
    │               ├── messages.go
    │               ├── mux.go
    │               ├── server.go
    │               ├── session.go
    │               ├── tcpip.go
    │               └── transport.go
    └── vendor.json
9 directories, 36 files

我们发现vendor.json的package已经记录了第三方包的版本,并且把这些依赖的包都放到vendor目录下了

根据自己的需求,选择是否将vendor目录做版本控制


一般只需要将vendor.json做版本控制即可,但是对于那些需要翻墙才能下载的包也可以直接将vendor都纳入版本控制 添加.ignore.git仅对vendor.json做版本控制

git init
echo "vendor/golang.or" .gitignore
git add .
git commit -m "test commit"
git push -u origin master
...
其他小伙伴安装依赖


其他小伙伴如果需要使用这个项目, 拉下该项目

git clone ssh://git@xxx/govendor_test.git
https_proxy=http://localhost:8123 govendor sync  (我需要安装golang.org的包,因此需要FQ)

这样就安装了该项目的指定版本的第三个依赖。

接下来愉快的玩耍吧!