在只有一个项目的情况下,GOPATH
非常合情合理而且简单,如果有多个项目,各个项目的源码也可以放在同一个GOPATH
之中,有着各自的包结构,使用着来自GOPATH
的非标准包,此时整个GOPATH
目录就是一个巨大的 repository,具称 Google 内部就是这样的场景,才会有GOPATH
这样的设计,Go 社区中也有着「如果必须切换GOPATH
,大概有哪些地方不对了」的说法。
问题在于,这并不是社区或其他公司中使用 Go 的方式,如果个别项目有个别的包,比较单纯的做法是各个项目有个专用的GOPATH
,想要开发哪个项目,就切换至该项目使用的GOPATH
,然而很快地,如果有项目依赖在这些个别项目上呢?将它们组织为巨大的 repository 是个做法,或者是令GOPATH=prj1:prj2:prj3
,prjx 是指向各项目源码的路径,也就是说GOPATH
会是一大串路径结合后的产物。
在上述的设定中,维持了一个GOPATH
不用切换,新项目可以加入至GOPATH
最前头,go get
的第三方包会下载到最前面的路径中,然而,若需要 prj2 也在开发中,若 prj2 需要新的第三方包时,go get
却会下载到新项目之中;在各自不同的情境中,无论怎么调整GOPATH
的顺序,总是会有各自不同的问题发生。
另一方面,GOPATH
本身不涉及包来源的版本问题,因此,若项目依赖的 repository 被修改了,日后构造项目就会受到影响,对依赖于Github之类来源,而且第三方包本身非常活跃的项目来说,重新构造项目时无法有稳定的结果,这显然是个大问题。
例如在〈Go 包管理〉中看过的例子,使用go get github.com/JustinSDK/goexample
,然后编写底下的程序:
package main
import "github.com/JustinSDK/goexample"
func main() {
goexample.Hi()
goexample.Hello()
}
这简单的程序被发布为一个范例了,某年某月的某一天,我修改了 goexample 的内容,让Hi
、Hello
显示中文并发布到 Github 上的文件库,有位读者,依旧照着〈Go 包管理〉中的说明进行操作,然而看到的不是英文,而是中文的招呼。
为了避免这个问题,通常会将下载的文件库复制出来,例如放到 deps 中:
project
└─src
├─deps
│ └─src
│ └─github.com
│ └─JustinSDK
│ └─goexample
│ .gitignore
│ hello.go
│ hi.go
│ LICENSE
│ README.md
│
└─main
main.go
问题是放到 deps 的文件库该怎么用呢?其中一个方式是修改import
:
package main
import "deps/src/github.com/JustinSDK/goexample"
func main() {
goexample.Hi()
goexample.Hello()
}
另一个方式是透过工具修改GOPATH
自动包含 deps 目录,这类的概念主要成为了godep等工具早期在管理 Go 包时的思考出发点。
Go 在 1.5 时实验性地加入了 vendor,需要透过GO15VENDOREXPERIMENT="1"
来启用,1.6 默认GO15VENDOREXPERIMENT="1"
,1.7 拿掉GO15VENDOREXPERIMENT
环境变量,使得vendor成为正式的内置特性。
简单来说,如果你的包中有个 vendor 文件夹,例如:
project
└─src
└─main
│ main.go
│
└─vender
└─github.com
└─JustinSDK
└─goexample
.gitignore
hello.go
hi.go
LICENSE
README.md
对于import "github.com/JustinSDK/goexample"
来说,寻找依赖包的顺序会变成 vendor -> GOROOT 的 src -> GOPATH 的 src。
在 vendor 推出后,godep
也改使用 vendor了,而glide等工具,也都基于 vendor 了。