Vendor目录介绍
随着Go 1.5 release版本的发布,vendor目录被添加到除了GOPATH
和GOROOT
之外的依赖目录查找的解决方案。在Go 1.6之前,你需要手动的设置环境变量GO15VENDOREXPERIMENT=1
才可以使Go找到Vendor目录,然而在Go 1.6之后,这个功能已经不需要配置环境变量就可以实现了。
Note,即使使用vendor,也必须在
GOPATH
中,在go的工具链中,你逃不掉GOPATH
的
GOPATH可以设置多个工程目录,linux下用冒号分隔(必须用冒号,fish shell的空格分割会出错,参见另一篇文章),windows下用分号分隔,但是go get 只会下载pkg到第一个目录,但是编译的时候会搜索所有的目录。
go查找依赖包路径的规则如下:
- 当前包下的
vendor
目录。 - 向上级目录查找,直到找到src下的
vendor
目录。 - 在
GOPATH
下面查找依赖包。 - 在
GOROOT
目录下查找
一些建议
在使用vendor中,给出如下建议:
- 一个library库工程(不包含
main
的package)不应该在自己的版本控制中存储外部的包在`vendor`目录中,除非他们有特殊原因并且知道为什么要这么做。 - 在一个app应用中,(包含
main
的package),建议只有一个vendor
目录在代码库一级目录。
上面建议的原因如下:
- 在目录结构中的每个包的实例,即使是同一个包的同一个版本,都会打到最终的二进制文件中,如果每个人都单独的存储自己的依赖包,会迅速导致生成文件的二进制爆发(binary bloat)
- 在一个目录的某个pacage类型,并不兼容在同一个package但是在不同目录的类型,即便是同一个版本的package,那意味着loggers,数据库连接,和其他共享的实例都没法工作。
举个例子
工程目录如下:
- $GOPATH/src/github.com/mattfarina/golang-broken-vendor
- foo.go
- vendor/
- a/
- b/
- vendor/a/
在这个例子中,两个a
package都是完全一样的,b package在代码库中保存了a package,在*应用代码中也引用了a包。
文件foo.go
做了很简单的事情:
func main() {
var it a.A
it = "foo"
b.Do(it)
}
那么问题来了,当我们build的时候,发现出问题了,返回了下面的错误:
$ GO15VENDOREXPERIMENT=1 go build
./foo.go:12: cannot use it (type "github.com/mattfarina/golang-broken-vendor/vendor/a".A) as type "github.com/mattfarina/golang-broken-vendor/vendor/b/vendor/a".A in argument to b.Do
你可以clone这个测试工程(https://github.com/mattfarina/golang-broken-vendor)到本地重现。
为什么用vendor目录
如果我们已经使用GOPATH
去存储packages了,问什么还需要使用vendor
目录呢?这是一个很实战的问题。
假如多个应用使用一个依赖包的不同版本?这个问题不只是Go应用,其他语言也会有这个问题。
vendor
目录允许不同的代码库拥有它自己的依赖包,并且不同于其他代码库的版本,这就很好的做到了工程的隔离。
推荐
我们发现Glide是非常好的包管理解决方案,他将依赖包平展开存放在*vendor
目录中,如果一个包被另一个程序引用了,那么这个包最好不要存储外部依赖项。如果使用Glide,你可以在glide.yml
文件中指定依赖包,Glide会帮你管理,并使用正确的版本。
golang语言工程目录结构:
- 设置GOPATH,这个环境变量指向你的projectDir(工程目录),形如:GOPATH=/home/user/ext:/home/user/projectDir (可以设置多个工程目录,linux下用冒号分隔,windows下用分号分隔)
- 创建工程文件夹projectDir
- 在projectDir下创建src目录,(表示源代码目录)
- 在src下创建用以区分各个包的容器文件夹local, (本地包/库的容器目录,但它本身不属于包的一部分)
- 在local下创建包pkgA目录,(本地包/库的目录)
- 在pkgA下创建package source源代码文件,这些文件的package都是pkgA,比如创建一个文件pkga.go,代码如下:
package pkgA import "fmt" func TestPrint(){
fmt.Print("Hello world \n")
}
写完源代码以后在src目录下运行go install local/pkgA命令把包pkgA打包成.a文件(会在projectDir/pkg目录下生成pkgA.a的目标文件)
在local下创建文件夹,取名helloDir。
在helloDir文件夹下创建带有main函数的源代码文件hello.go,代码如下:
package main import (
"fmt"
"local/pkgA"
) func main(){
fmt.Print("main package~\n")
pkgA.TestPrint()
}
在src下运行go install local/helloDir (会创建projectDir/bin目录,并生成以helloDir 为文件名的可执行文件)。需要注意的是要生成可执行文件的话,go install后的文件夹下一定要有一个或多个属于package main包的go源文件(即源代码里第一行为 package main)。
最后projectDir目录下的结构类似如下的形式:
.
├── bin
│ └── helloDir # executable
├── pkg
│ └── linux_amd64
│ └── local
│ └── pkgA.a # package object of pkgA
└── src
└── local
├── helloDir
│ └── hello.go # source code of package main, 可以有多个文件同时属于 package main。 至少有一个属于package main的文件才能编译出可执行文件。
└── pkgA
└── pkga.go # package source
GOPATH环境变量为(go env | ack GOPATH):
GOPATH="/home/hzh/develop/go:/home/hzh/temp/go/projectDir"
======================================================================================================================
如果要使用glide来管理package,则在src目录下运行 glide init,然后编辑 glide.yaml ,去掉本地库的下载(使用 ignore),典型的glide.yaml文件如下:
package: .
import:
- package: github.com/pkg/errors
version: ^0.8.
ignore:
- local/pkgA
另外,go build 和 go install 及 go run 的区别:
go install 是针对 package的,而 go build 和 go run 是针对某文件的。对于 go build 可以是任意文件,对于go run这个文件必须属于package main。
go build 编译package main时,生成的可执行文件在当前目录,而 go install 编译 package main 时,生成的可执行文件在项目的bin目录下。
go build 和 go install 编译普通package时(非package main),生成的库都在项目的pkg目录下。
go run 只可以编译包含main()函数的那个.go文件,且立即执行文件。
go build 用于编译我们指定的源码文件或代码包以及它们的依赖包。,但是注意如果用来编译非命令源码文件(即非可执行文件),即库源码文件,go build 执行完是不会产生任何结果的。这种情况下,go build 命令只是检查库源码文件的有效性,只会做检查性的编译,而不会输出任何结果文件。
注意,不管是以go build 或者 go install 还是 go run 的方式来编译glide所管理的项目,所有的文件都必须位于其相应的package里,不允许某文件不位于任何package里,不然编译不会成功。以下面的projectDir项目为例,hello.go属于main package,如果将它移动到src下(不属于任何目录),此时用go build、go install 及 go run 都编译不成功,提示找不到imported package(引用的外部package,也即vendor里的package)。
对于上面的项目,使用glide来管理的话,项目目录结构为:
projectDir/
├── bin
│ └── helloDir
├── pkg
│ └── linux_amd64
│ ├── local
│ │ └── pkgA.a
│ └── vendor
│ └── github.com
│ └── pkg
│ └── errors.a
└── src
├── glide.lock
├── glide.yaml
├── local
│ ├── helloDir
│ │ └── hello.go
│ └── pkgA
│ └── pkga.go
└── vendor
GOPATH环境变量的值为 GOPATH="/home/hzh/develop/go:/home/hzh/temp/go/projectDir"
glide.yaml 的内容为:
package: .
import:
- package: github.com/pkg/errors
version: ^0.8.
ignore:
- local/pkgA
hello.go的内容为:
package main import (
"fmt"
"local/pkgA"
"reflect" "github.com/pkg/errors"
) func main() {
fmt.Print("main package~\n")
pkgA.TestPrint()
err := errors.New("hzh")
fmt.Println("%T", err)
fmt.Println(reflect.TypeOf(err).PkgPath())
}
pkga.go的内容为:
package pkgA import "fmt" func TestPrint() {
fmt.Print("Hello world \n")
}
编译方法:
go build local/helloDir 或
go install local/helloDir 或
go run local/helloDir/hello.go
若由git来管理项目的版本,则.git 及 .gitignore 位于 src 目录下,.gitignore 的内容必须包括 vendor/ 。
如果要使用glide来管理package,以下是最标准的目录结构:
/home/hzh/hzh/dev/goo/src/projectDir
├── glide.lock
├── glide.yaml
├── local
│ ├── helloDir
│ │ └── hello.go
│ └── pkgA
│ └── pkga.go
└── vendor
GOPATH环境变量的值为 GOPATH="/home/hzh/hzh/dev/go:/home/hzh/hzh/dev/goo"
glide.yaml 的内容为(直接在projectDir目录下执行 glide init 命令):
package: projectDir
import:
- package: github.com/pkg/errors
hello.go的内容为:
package main import (
"fmt"
"projectDir/local/pkgA"
"reflect" "github.com/pkg/errors"
) func main() {
fmt.Print("main package~\n")
pkgA.TestPrint()
err := errors.New("hzh")
fmt.Println("%T", err)
fmt.Println(reflect.TypeOf(err).PkgPath())
}
pkga.go的内容为:
package pkgA import "fmt" func TestPrint() {
fmt.Print("Hello world \n")
}
这种最标准方法的好处是在任何目录里(切换到 /tmp 目录,自己试试),都可以使用如下编译方法来编译任何项目:
go build projectDir/local/helloDir 或
go build projectDir/local/helloDir/hello.go 或
go install projectDir/local/helloDir 或
go run local/helloDir/hello.go 或 (先cd 到projectDir目录)
go run helloDir/hello 或 (先cd 到projectDir/local目录)
go run hello (先cd 到projectDir/local/helloDir目录)
由上面的目录结构可以看出,go的package路径实际上是从 ${GOPATH}/src 开始算的,上面的最标准的例子中,package路径即是从 projectDir 开始算的,中间的local目录也算package的路径,因此它是package 路径(但不是package名);而projectDir属于package的路径起始位置,也属于package路径(但不是package名);而 helloDir 即是package路径也是package名(但由于该package没有被外部引用,所以package路径与package名可以不相同,即 helloDir != main,其中main是package名)。真正的package名是由 .go 源文件声明的,如果该package会被其它文件/package所引用,则申明的package名必须与路径最后的目录名相同,不然编译通不过。强烈建议任何时候都保持package名与路径的最后一个目录名相同,不管该package是否被外部所引用,因为很难保证现在不被引用的package将来永远不会被引用。
golang使用vendor目录来管理依赖包的更多相关文章
-
Golang 解决 Iris 被墙的依赖包
使用 Golang 的 Iris web 框架时,用 go get github.com/kataras/iris 命令久久无法下载,最后还报一堆错误. 使用 GOPROXY 可解决问题,也可参考如 ...
-
017-通过govendor管理依赖包
1:安装 go get -u github.com/kardianos/govendor 2:配置环境变量 需要把 $GOPATH/bin/ 加到 PATH 中 D:\my_workspace\go_ ...
-
go module管理依赖包
go mod 最大的好处就是摆脱了GOPATH这个限制,在除了GOPATH以外的目录下也能开展你的项目 go mod使用: 1,确保你的go版本是1.1以上 2,创建一个项目目录example,并添加 ...
-
Vue笔记:使用 Yarn 管理依赖包
上年10月份, Facebook 发布了新的 node.js 包管理器 Yarn 用以替代 npm ,它比npm更快.更高效. Yarn VS npm 1.yarn.lock 文件 在 npm 中同样 ...
-
go依赖包管理工具vendor基础
go依赖包管理工具vendor基础 vendor是go的依赖包管理工具,主要用于管理项目中使用到的一些依赖. 它将项目依赖的包,特指外部包,复制到当前工程下的vendor目录下,这样go build的 ...
-
go mod 无法自动下载依赖包的问题
go 11以后启用了go mod功能,用于管理依赖包. 当执行go mod init生成go.mod文件之后,golang在运行.编译项目的时候,都会检查依赖并下载依赖包. 在启动了go mod之后, ...
-
引入HBase依赖包带来的麻烦
在一个项目里用到HBase做底层存储,使用maven来管理相关Jar包依赖,用maven来管理依赖包,特别不爽的就是他会将你引入Jar包自己的依赖都搞进来,经常会出现一些类和方法冲突找不到等状况.这次 ...
-
有关项目依赖包发生 Manifest Merge 冲突的详细解决方案
安卓开发使用 Gradle 插件管理依赖包确实非常方便,尤其是在解决一些依赖冲突的问题上.比如,重复依赖的问题,具体内容请我之前写的一篇文章: 有关 Android Studio 重复引入包的问题和解 ...
-
为什么在SpringBoot+maven的项目中,所引入的依赖包可以不指定依赖的版本号?
当在Springboot项目中引入了spring-boot-starter-parent,则可以不用引入依赖包版本号,比如: <parent> <groupId>org.spr ...
随机推荐
-
iOS 删除、重新排序xcdatamodel
找到Xcode项目文件.xcodeproj,查看包内容. 里面有project.pbxproj,用文本编辑器打开. 找到类似如下内容段: /* Begin XCVersionGroup section ...
-
c++中vector的用法详解
c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...
-
iOS Node Conflict svn冲突
当出现这个冲突时,应该是我add的文件,和同事处理的方法有冲突导致的. 这个的解决办法是:先revert这个文件,然后再update.
-
根据字符串计算UILabel尺寸
iOS开发中经常会遇到UILabel大小尺寸不固定的情况,需要根据文字内容变化,这时候就需要计算文字大小以自动改变UILabel的尺寸. iOS7之后计算尺寸只需要一个方法就可以: - (CGSize ...
-
LDF文件过大的解决办法
检查扎兰屯服务器的时候,发现其中一个分区的原空间有300多个G,但只余下了80多个G.检查了一下,发现某库ldf文件过大,竟然达到了280多个G. 这如何得了,再这样下去,硬盘怎能受得了? 尝试用收缩 ...
-
web开发之负载均衡的简单架构
负载均衡 负载均衡的核心思想就是:请求分担 最简单的配置: 一台负载均衡服务器 两台webserver服务器 两台webserver服务器需要配置相同的服务器环境,设置相同的域名指向 负载均衡服务器需 ...
-
使用go, gin, gorm编写一个简单的curd的api接口
go 是一门非常灵活的语言,既具有静态语言的高性能,又有动态语言的开发速度快的优点,语法也比较简单,下面是通过简单的代码实现了一个简单的增删改查 api 接口 hello world 常规版 新建 d ...
-
VisualStudio下如何编译和使用最新版本的OpenCV(修正版)
OpenCV是托管于GitHub的开源项目,本文具体解决一个问题,就是“获取最新版本的OpenCV,并且在自己的项目中使用起来" 最新版本 2017年3月31日 BY:jsxyhelu ...
-
Linux运维常见故障排查和处理的33个技巧汇总
作为linux运维,多多少少会碰见这样那样的问题或故障,从中总结经验,查找问题,汇总并分析故障的原因,这是一个Linux运维工程师良好的习惯.每一次技术的突破,都经历着苦闷,伴随着快乐,可我们还是执着 ...
-
poj 1127(直线相交+并查集)
Jack Straws Description In the game of Jack Straws, a number of plastic or wooden "straws" ...