govendor

如果一个包在vendor和GOPATH下面都存在那么谁会优先使用呢。
结论是:



优先使用vendor目录下面的包。
如果vendor下面没有搜索到,再搜索GOPATH下面的包。
要么完整使用vendor下面的包,要么完整使用GOPATH下面的包,不会混合使用:
-3.1 假如一个函数定义再GOPATH下面的包里,而没有定义在vendor路径下的同名包里,那么调用者就会报函数未定义错误,因为调用者如果找到有vendor路径下面的包,就不会去找GOPATH下面的包了。

包mydeps在vendor目录下面和GOPATH路径下面都存在了,那么main.go引用的时候只会引用vendor下面的mydeps(src/myproject/vendor/mydeps),而忽略GOPATH下面的mydeps包(src/mydeps)。



前面提到GOPATH和PATH类似,可以包含多个路径,中间用分号隔开,go在搜索包的时候会按手续从前往后搜搜。那么vendor怎么处理层级关系呢。



规则是:



1.从引用文件所在的vendor路径下面搜索。
2.如果没有找到,那么从上层目录的vendor路径下面搜索。
3.直到src的vendor路径下面搜索。



如果src/mydep/mydep1/mydep.go引用了myvendor1和myvendor,那是怎么搜索的呢



先从src/mydep/mydep1/vendor下面搜索myvendor1。
找到了,直接使用。
先从src/mydep/mydep1/vendor下面搜索myvendor。
发现没有找到,那么从上层路径搜索,即:
先从src/mydep/vendor下面搜索myvendor。
找到了,直接使用。
如果还没有找到,那么继续向上一级搜索,即
src/vendor
如果找到了,则使用;如果还没有找到,那么继续从GOPATH里搜索,直到找到或者失败。



建议golang项目严格按照golang项目组织方式,即使只是一个自包含的项目。



|-- src
|-- mainpackage
|-- XXX.go
|-- YYY.go
|-- vendor
|-- deppackage1
|-- XXX1.go
|-- YYY1.go
|-- vendor
|-- deppackage2
|-- XXX2.go
|-- YYY2.go
|-- vendor
|-- VVV1.go
|-- VVV2.go
|-- vendor

GOPATH使用分号(:)隔开的多个路径。
go编译的时候会从GOPATH/src目录下面搜索import的包。
vender目录放在源文件目录同级,下面包含各个包。
3.1 vendor的搜索优先于GOPATH的搜索。
3.2 vendor按照路径深度向外按顺序搜索,直到$GOPATH/src/vendor为止。

golang的 GOPATH和vendor的搜索关系

基本规则

所有的go文件都是必须组织成包的形式,放在相应文件夹下:
1.1 建议包名和文件夹名字相同;虽然也可以不同,但会引发使用误解。
1.2 对于主程序包,也需要放在文件夹下面,注意:
1.2.1 不建议使用main作为文件夹名,虽然这个包名是main。
1.2.2 也不建议使用src作为文件名,尽管这是允许的,但是会引发误解。
1.2.3 建议使用项目名字作为包名。
go build命令如果不带参数,就是build当前包,当前目录所在的包,即当前目录下面的所有go文件。
1.2 如果go build指定了目标包,那么就会从GOPATH路径下面搜索包,如果找不到,就报失败;哪怕当前路径就在目标包里,但是GOPATH没有包含,也会报失败。
1.2 如果GOPATH没有设置,其缺省路径就是$HOME/gp
例子1:完全自包含项目

项目只有一个包,即main包,没有引用其他的包(golang自带的系统包除外)。

1.新建文件夹,例如myproject。
mkdir myproject
编辑项目文件
[~/myproject]$ cat main.go
package main

import "fmt"

func main() {
fmt.Printf("main::main\n");
foo()
}

[~/myproject]$ cat foo.go
package main

import "fmt"

func foo() {
fmt.Printf("main::foo\n");
}
编译项目
[~/myproject]$ unset GOPATH
[~/myproject]$ go build
[~/myproject]$ ls -1
foo.go
main.go
myproject
直接进入项目目录运行 go build,即编译当前包。
不需要设置GOPATH值,缺省就是~/go,因为这是一个自包含项目,不需要引用GOPATH的任何值。
编译生成的可执行文件名就是项目文件夹名。
注意当前目录必须是项目文件所在目录,因为go build没有指定目标包,缺省编译当前目录包;如果不是就不行,那得必须按照golang的项目组织规范来组织。

|-- src
|-- myproject
|-- main.go
|-- foo.go
然后设置GOPATH=path/to/,再运行go build myproject,这样就可以在任何目录下面编译,编译生成的可执行文件就在编译所在的目录下,而不是包源文件所在的目录。

例子2:引用了其他的包

基本规则:

1.import 总是从$GOPATH/src目录下面搜索包,如果找不到就报错。
1.2 并不会从当前目录下面去搜索,也不会从源文件相对目录下面去搜索。
1.GOPATH可以包含多个路径,中间用冒号(:)隔开,就像PATH一样。
鉴于此,建议golang项目必须严格按照规范的目录结构组织,哪怕是前面这种自包含的项目。

例子3:vendor目录的使用

基本规则:

1.使用vendor,项目必须严格按照规范的目录结构组织。
1.2 即使像例子1中自包含的项目也不能使用vendor
2.vender需要在原文件下面创建vendor目录,然后把vendor的文件包放入vendor目录即可,在引用的时候不需要指定vendor路径。

[~/]$ find

/src
/src/myproject
/src/myproject/main.go
/src/myproject/vendor
/src/myproject/vendor/mydeps
/src/myproject/vendor/mydeps/dep1.go

[~/]$ cat /src/myproject/main.go
package main

import "fmt"
import "mydeps"

func main() {
fmt.Printf("main::main\n");
mydeps.Foo()
}

[~/]$ cat /src/myproject/vendor/mydeps/dep1.go
package mydeps

import "fmt"

func Foo() {
fmt.Println("in mydeps::Foo")
}


1. govendor简介
golang工程的依赖包经常使用go get命令来获取,例如:go get github.com/kardianos/govendor ,会将依赖包下载到GOPATH的路径下。

常用的依赖包管理工具有godep,govendor等,在Golang1.5之后,Go提供了 GO15VENDOREXPERIMENT 环境变量(Go 1.6版本默认开启该环境变量),用于将go build时的应用路径搜索调整成为 当前项目目录/vendor 目录方式。通过这种形式,我们可以实现类似于 godep 方式的项目依赖管理。

2. 安装与使用
2.1. 安装
go get -u -v github.com/kardianos/govendor
2.2. 使用
复制代码
#进入到项目目录
cd /home/gopath/src/mytool

#初始化vendor目录
govendor init

#查看vendor目录
[root@CC54425A mytool]# ls
commands main.go vendor mytool_test.sh

#将GOPATH中本工程使用到的依赖包自动移动到vendor目录中
#说明:如果本地GOPATH没有依赖包,先go get相应的依赖包
govendor add +external
或使用缩写: govendor add +e

#Go 1.6以上版本默认开启 GO15VENDOREXPERIMENT 环境变量,可忽略该步骤。
#通过设置环境变量 GO15VENDOREXPERIMENT=1 使用vendor文件夹构建文件。
#可以选择 export GO15VENDOREXPERIMENT=1 或 GO15VENDOREXPERIMENT=1 go build 执行编译
export GO15VENDOREXPERIMENT=1
复制代码
2.3. 说明
govendor只是用来管理项目的依赖包,如果GOPATH中本身没有项目的依赖包,则需要通过go get先下载到GOPATH中,再通过govendor add +external拷贝到vendor目录中。Go 1.6以上版本默认开启GO15VENDOREXPERIMENT环境变量。

2.3. 常用命令
常见的命令如下,格式为 govendor COMMAND。

命令 功能
init 初始化 vendor 目录
list 列出所有的依赖包
add 添加包到 vendor 目录,如 govendor add +external 添加所有外部包
add PKG_PATH 添加指定的依赖包到 vendor 目录
update 从 $GOPATH 更新依赖包到 vendor 目录
remove 从 vendor 管理中删除依赖
status 列出所有缺失、过期和修改过的包
fetch 添加或更新包到本地 vendor 目录
sync 本地存在 vendor.json 时候拉去依赖包,匹配所记录的版本
get 类似 go get 目录,拉取依赖包到 vendor 目录

使用grpc进行开发时,启动grpc client报错如下:

panic: /debug/requests is already registered. You may have two independent copies of golang.org/x/net/trace in your binary, trying to maintain separate state. This may involve a vendored copy of golang.org/x/net/trace.
1
错误原因
golang.org/x/net/trace 在整个$GOPATH中有重复的副本,包括其他项目的vendor目录中依赖了golang.org/x/net包。

错误提示

goroutine 1 [running]:
golang.org/x/net/trace.init.0()
/Users/lcl/go/src/golang.org/x/net/trace/trace.go:123 +0x17e

查看golang.org/x/net/trace/trace.go文件中的init()方法
// HTTP ServeMux paths.
const (
debugRequestsPath = "/debug/requests"
debugEventsPath = "/debug/events"
)

...

func init() {
_, pat := http.DefaultServeMux.Handler(&http.Request{URL: &url.URL{Path: debugRequestsPath}})
if pat == debugRequestsPath {
panic("/debug/requests is already registered. You may have two independent copies of " +
"golang.org/x/net/trace in your binary, trying to maintain separate state. This may " +
"involve a vendored copy of golang.org/x/net/trace.")
}

// TODO(jbd): Serve Traces from /debug/traces in the future?
// There is no requirement for a request to be present to have traces.
http.HandleFunc(debugRequestsPath, Traces)
http.HandleFunc(debugEventsPath, Events)
}

解决办法
使用包管理工具govendor,用于将go build时的应用路径搜索调整成为当前项目目录/vendor目录方式 推荐
在$GOPATH目录下只保留一份golang.org/x/net/trace,删除所有项目vendor目录中的golang.org/x/net/trace 不推荐
独立工程目录,每个项目设置GOPATH 不推荐

Vendor目录介绍
Golang 官方并没有推荐最佳的包管理方案。到了1.5版本时代,官方引入包管理的设计,加了 vendor 目录来支持本地包管理依赖。官方 wiki 推荐了多种支持这种特性的包管理工具,如:Godep、gv、gvt、glide、govendor等。即使使用vendor,也必须在GOPATH中。

查找依赖包路径的顺序
当前包下的vendor目录。
向上级目录查找,直到找到src下的vendor目录。
在GOPATH下面查找依赖包。
在GOROOT目录下查找
包管理工具govendor
需要把 $GOPATH/bin/ 加到 PATH 中。

安装
go get -u github.com/kardianos/govendor
命令
init 创建 vendor 文件夹和 vendor.json 文件
list 列出已经存在的依赖包
add 从 $GOPATH 中添加依赖包,会加到 vendor.json
update 从 $GOPATH 升级依赖包
remove 从 vendor 文件夹删除依赖
status 列出本地丢失的、过期的和修改的package
fetch 从远端库增加新的,或者更新 vendor 文件中的依赖包
sync Pull packages into vendor folder from remote repository with revisions
migrate Move packages from a legacy tool to the vendor folder with metadata.
get 类似 go get,但是会把依赖包拷贝到 vendor 目录
license List discovered licenses for the given status or import paths.
shell Run a "shell" to make multiple sub-commands more efficient for large projects.

go tool commands that are wrapped:
`+` package selection may be used with them
fmt, build, install, clean, test, vet, generate, tool

Category golang