设为首页 收藏本站
查看: 1237|回复: 0

[经验分享] golang使用vendor目录来管理依赖包

[复制链接]

尚未签到

发表于 2018-9-20 12:17:58 | 显示全部楼层 |阅读模式
Vendor目录介绍
  随着Go 1.5>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
  我们发现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.0  
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.0  
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将来永远不会被引用。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598860-1-1.html 上篇帖子: golang Linux下编译环境搭建 下篇帖子: golang 标准库间依赖的可视化展示
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表