Golang 的包管理机制

在 Go1.5 之前用 GOPATH 以及 GOROOT 这两个环境变量来管理包的位置,GOROOT 为 Go 的安装目录,以及编译过程中使用到的系统库存放位置,如 fmt。Go1.5 到 Go1.7 开始稳定到 Vendor 方式,即依赖包需要放到 $GOPATH/src/vendor 目录下,这样每个项目都有自己的 vendor 目录,但是如果依赖同样的三方包,很容易造成资源重复,Go vendor 出现了几种主流的管理工具,包括 godep、govendor、golide 等。

在 Go1.11 之前,GOPATH 是开发时的工作目录,其中包含三个子目录:

src 目录:存放 go 项目源码和依赖源码,包括使用 go get 下载的包
bin 目录:通过使用 go install 命令将 go build 编译出的二进制可执行文件存放于此
pkg 目录:go 源码包编译生成的 lib 文件存储的地方
在 Go1.11 之前,import 包时的搜索路径

GOROOT/src: 该目录保存了 Go 标准库代码(首先搜寻导入包的地方)
GOPATH/src: 该目录保存了应用自身的各个包代码和第三方依赖的代码
./vendor :vendor 方式第三方依赖包(如果支持 Vendor)
在 Unix 和类 Unix 系统上,GOPATH 默认值是 $HOME/go,Go1.11 版本后,开启 GO Modules 后,GOPATH 的作用仅仅为存放依赖的目录了。

在 Go 的 1.11 版本之前,GOPATH 是必需的,且所有的 Go 项目代码都要保存在 GOPATH/src 目录下,也就是如果想引用本地的包,你需要将包放在 $GOPATH/src 目录下才能找得到。Go 的 1.11 版本之后,GO 官方引入了 Go Modules,不仅仅方便的使用我们的依赖,而且还对依赖的版本进行了管理。

在 Go1.11 后通过 go mod vendor 和 -mod=vendor 来实现 Vendor 管理依赖方式。本来在 vgo 项目(Go Modules 前身)是要完全放弃 vendor,但是在社区反馈下还是保留了。总之就是在 Go.1.11 之后需要开启 Go Modules 条件下才能使用 Vendor,具体地感兴趣或还沿用了 Vendor 的朋友可以去了解下,不过建议以后仅使用 Go Modules 包管理方式了。

dep/govendor 机制

vendor 使用限制

使用 vendor 来管理包的项目,必须位于$GOPATH/src 下面。

vendor 目录和 json 文件

该工具将项目依赖的外部包拷贝到项目下的 vendor 目录下,并通过 vendor.json 文件来记录依赖包的版本,方便用户使用相对稳定的依赖。

vendor 机制下,如何搜索包依赖

那么查找依赖包路径的解决方案如下:

  • 当前包下的 vendor 目录。
  • 向上级目录查找,直到找到 src 下的 vendor 目录。
  • 在 GOPATH 下面查找依赖包。
  • 在 GOROOT 目录下查找

如果我们已经使用 GOPATH 去存储 packages 了,问什么还需要使用 vendor 目录呢?

这是一个很实战的问题。假如多个应用使用一个依赖包的不同版本?这个问题不只是 Go 应用,其他语言也会有这个问题。
vendor 目录允许不同的代码库拥有它自己的依赖包,并且不同于其他代码库的版本,这就很好的做到了工程的隔离。
每个项目都有各自的 vendor,每个 vendor 可以存放不同版本的依赖包。

module 机制

在 go1.11 版本中,新增了 module 管理模块功能,用来管理依赖包。要知道,在这个之前,想要对 go 语言包进行管理,只能依赖第三方库实现,比如 Vendor,GoVendor,GoDep,Dep,Glide 等等,对于初学者来说,真的是选择困难症。

开启 module 特性

要开始使用 go module 的特性, 需要先设置 GO111MODULE 环境变量。
开启 GO111MODULE。
要使用 go module,首先要设置 GO111MODULE=on,这没什么可说的,如果没设置,执行命令的时候会有提示,这个大家应该都了解了

在$GOAPTH/src 中创建项目

在$GOPATH/src 目录下创建 github.com/cnwyt/mytest 目录,mytest 为项目目录

1
mkdir -p $GOPATH/src/github.com/cnwyt/mytest
1
2
3
$ export GO111MODULE=on
$ go mod init github.com/cnwyt/mytest
go: creating new go.mod: module github.com/cnwyt/mytest

当然这个 go 模块可以创建在任意位置,不强制邀请放在 GOPATH 路径下。

在$GOPATH/src 外也可以创建项目

在 GOPATH 以外的模块,创建一个 helloworld 目录,用来调用刚刚创建的 mytest 模块。

1
2
$ mkdir helloworld && cd helloworld
$ vi main.go

创建一个 main.go 文件:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"
//import "github.com/cnwyt/mytest"

func main() {
fmt.Println("Hello, World!");

//mytest.ShowTest1()
}

初始化该模块,引入 github.com/cnwyt/mytest 模块,指定版本为 latest:

1
2
3
$ go mod init helloworld
$ go mod edit -require github.com/cnwyt/mytest@latest

初始化后,会生成一个 go.mod 文件,类似 npm 里的 package.json 或者 composer 的 composer.json 的一个文件。

1
2
3
4
module helloworld

require github.com/cnwyt/mytest v0.0.0

这样直接执行 go test 或者 go run main.go 会报错:

1
2
3
$ go test
build helloworld: cannot find module for path github.com/cnwyt/mytest

这是为啥呢? 这是因为我们虽然创建了一个名为 github.com/cnwyt/mytest 模块,在 GOPATH 路径里也有这个模块。但是,GO 模块去 Github 去找这个模块,而不是在 GOPATH 路径里去找,所以找不到。

那该怎么办呢?
有两个解决办法:
第一个办法,很简单,就是直接将 cnwyt/mytest 模块推送的 GitHub 上。
但是,如果我要修改 cnwyt/mytest 里的代码,都得先推送到 GitHub 上,才能生效,实在太麻烦了。
那就直接使用第二个办法, 使用 go replace:

直接修改 go.mod,新增一行 replace:

1
2
3
4
5
6
module helloworld

require github.com/cnwyt/mytest v0.0.0

replace github.com/cnwyt/mytest => /Users/wangtom/goworkspace/mytest

注意版本号必须填写,可以填 v0.0.0 或者 latest.

调用第三方模块

比如项目中会用到比较流行的路由模块 gorilla/mux:

直接修改 go.mod,新增一行 require,不指定版本可以直接写 latest 获取最新版本:

1
require github.com/gorilla/mux latest

运行 go build 或 go test 会自动从 GitHub 下载模块,并会修改 go.mod 文件。

比如运行后会把 latest 直接修改成目前最新的版本 v1.6.2 :

1
2
3
4
5
6
7
module helloworld

require github.com/cnwyt/mytest v0.0.0
require github.com/gorilla/mux v1.6.2

replace github.com/cnwyt/mytest => /Users/wangtom/goworkspace/godict

可以看到模块 gorilla/mux 代码会下载到 $GOPATH/pkg/mod/ 模块下:

1
2
3
4
$ ll /Users/wangtom/goworkspace/pkg/mod/github.com/gorilla
total 0
dr-xr-xr-x 22 wangtom staff 704B 12 24 22:14 mux@v1.6.2

不使用 vendor 和 module 机制时,可以手动下载所有依赖

在不使用 vendor 和 module 的情况下,可以使用较原始的方式,将代码中依赖的内容全部下载并编译在$GOPATH 路径下。
在项目目录下,执行如下命令:
go get -d -v ./…