Go 引用的包,可以分为两类:内部包和外部包,对于内部包,因为是项目内部自己写的,管理起来比较简单;但是,对于外部包,因为是开源的,一般由第三方维护管理,可能会版本不一致,出现非预期的情况,导致编译错误,或者出现程序 BUG,有经验的程序员应该都知道,这种由于第三方包引起的问题,定位和解决往往都是很烦人的。所以,目前所谓的依赖包管理工具,都是针对外部包的管理。
大部分编程语言都是将项目代码目录作为单独的工作目录(workspace),但 Go 只有一个工作目录 ——
$GOPATH,因为 Go 自身提供了很多工具,固定的工作目录,便于这些工具的运行。因此,需要强调一点是,我们应该在 $GOPATH/src 下,创建自己的项目目录。因为,项目构建时,就是从这个路径下开始的(即作为环境路径)。
其实,对于 Go 本身来说,是不区分内部包和外部包的,因为 go get 下载的包,也都放在 $GOPATH/src 目录下,因此,当我们 import 依赖包时,只要直接路径指定就行了,类似 github.com/xx/x。又因为 $GOPATH 目录下可能创建了多个项目,因此下载在这里的依赖包,也可能会被多个项目引用,随时可能会被其他项目更新修改。
实际上对于内部包,我们在引用时可以使用相对路径(相对 import 代码的路径,例如: ./a/b 或者 ../x/y 等),这可能是 go 命令的潜规则,这种方式相对较灵活而且直观,项目存放位置也可以很随意。所以对于简单项目,个人是比较喜欢使用这种方式的。
但是,这会影响 dep 生成依赖关系,导致 dep 误以为没有依赖的外部包,非常奇怪的问题。
其实,这两个坑都是因为我之前比较随意,没有按规范,将项目放在 $GOPATH/src 导致的。
Go Modules
dep 还没有应用推广,在 Go 1.11 中,又推出了全新的依赖包管理机制 Go Modules,原来叫 vgo ,在 Go 1.11 被采纳合并到了主分支,正式被发布,不出意外的话,后续版本应该都会将这个当作包依赖管理的官方解决方案。
模块为 GOPATH 提供了替代方案,用来为项目定位依赖和管理版本化。如果 go 命令在 $GOPATH/src 之外的目录中运行,并且该目录中有一个模块的话,那么模块功能就会启用,否则 go 将会使用 GOPATH(Go 1.11)。与 dep 不同,模块是集成在 go 命令的,可以使用 go mod init 创建。
小结
Go 作为比较新的语言,包依赖管理方式也在持续变动,主要分为三个阶段: 纯依赖包源码拷贝 --> 基于 vendor --> Go Modules。
每次变动都使管理变的更先进,这对于 Go 和 Gopher 都是有益的,特别是 Go Modules 据称使用了其它语言包管理工具不同的理念算法,看起来比较复杂。因为刚出来,有较多问题需要讨论,后续比较成熟了再研究,可能要等到 Go 1.12 版本吧。
Go 依赖管理
Go 引用的包,可以分为两类:内部包和外部包,对于内部包,因为是项目内部自己写的,管理起来比较简单;但是,对于外部包,因为是开源的,一般由第三方维护管理,可能会版本不一致,出现非预期的情况,导致编译错误,或者出现程序 BUG,有经验的程序员应该都知道,这种由于第三方包引起的问题,定位和解决往往都是很烦人的。所以,目前所谓的依赖包管理工具,都是针对外部包的管理。
大部分编程语言都是将项目代码目录作为单独的工作目录(workspace),但 Go 只有一个工作目录 —— $GOPATH,因为 Go 自身提供了很多工具,固定的工作目录,便于这些工具的运行。因此,需要强调一点是,我们应该在
$GOPATH/src
下,创建自己的项目目录。因为,项目构建时,就是从这个路径下开始的(即作为环境路径)。 其实,对于 Go 本身来说,是不区分内部包和外部包的,因为go get
下载的包,也都放在$GOPATH/src
目录下,因此,当我们import
依赖包时,只要直接路径指定就行了,类似github.com/xx/x
。又因为 $GOPATH 目录下可能创建了多个项目,因此下载在这里的依赖包,也可能会被多个项目引用,随时可能会被其他项目更新修改。所以,在最开始的时候(Go 1.5 之前),go 项目包的依赖管理是很简陋的,为了保证外部包的安全,就必须要把外部包拷贝到项目内部,变成“内部包”。此时,包的查找顺序是: $GOROOT --> $GOPATH.
在 Go 1.5 时,为了解决这个问题,引入了
vendor
这一特殊目录,该目录在项目内创建,这就相当于,每个项目有了自己独立的GOPATH
,此时,包的查找顺序是:离引用代码最近的vendor
--> $GOROOT --> $GOPATH. 项目内的每个目录下都能创建自己的vendor
目录,引用时,使用的是最近一层的vendor
。但是,最好只在根目录创建vendor
,便于管理,也避免同样的包,出现在同一项目内的多个vendor
目录下。基于
vendor
能够实现真正意义的依赖包管理,而告别简陋的拷贝操作。内部包管理
内部包管理其实很简单,但是有一个特性需要说明,那就是
internal
目录,位于internal
目录下的包,只能被同父目录下的包引用,否则会编译报错。 这里需要说明的是,父目录的父目录也算,即位于项目根目录下internal
里面的包,是可以被项目内所有包引用的。 与vendor
类似,项目内也可以有多个internal
目录,但是建议在项目根目录创建一个就够了。internal
目录的目的是保护该目录下的包被其他项目所引用,例如某个包还不太成熟,不想被其他项目使用。很明显,这只是一种简单的“警告”机制。外部包管理
基于
vendor
实现的依赖包管理工具,比较流行的有dep
,Godep
,Glide
,Govendor
等,实现原理大同小异。官网这里对这几个工具进行了分类说明,建议使用dep
作为项目的依赖管理工具,原因主要有两点:dep
是官方推出的管理工具,虽然目前还是处于official experiment.
状态,但是已经可以作为正式工具使用了;Godep
已经是 Gopher 比较常用的依赖工具了,但是可以看到,其官网已经声明后面只会进行维护性的开发工作,建议大家使用dep
或其他工具代替:Please use dep or another tool instead.
dep 使用
dep 的使用,官方有较详细的说明文档:dep introduction.
这里根据自己的理解,做些说明。
主要有两个安装方式:
$GOPATH/bin
目录下:当然你也可以使用源码安装,但是,你最好不要这么干。安装完成后,执行
dep -h
查看帮助信息。dep init
首先进入需要 dep 管理依赖包的项目,以 dep-test 项目为例。执行命令dep init
完成后,在项目根目录下会创建文件
Gopkg.lock
,Gopkg.toml
和vendor
目录,vendor
里面就是依赖的包源码了,前两个文件保存内保存了依赖包的规则类型信息,依赖包版本信息以及依赖关系描述,后面还会对 dep 实现原理进行简单说明。dep ensure
自动下载项目所有引用的依赖包dep ensure add
下载指定路径的引用包,当有新的包引用时,尽量使用这种方式,而不是偷懒直接用dep ensure
,因为这个指令会将引用包版本信息自动写入文件Gopkg.toml
,否则只会更新文件Gopkg.lock
;dep ensure -update
更新依赖包版本,可以指定包路径,否则会更新所有依赖包;dep ensure -vendor-only
只会根据当前的Gopkg.lock
文件内包信息下载依赖包,而不会根据项目引用,自动更新Gopkg.lock
;dep ensure --no-vendor
会更新Gopkg.lock
,但是不会更新vendor
;dep check
检查vendor
里面的包源码是否与 dep 记录的信息一致,如果不一致,则使用dep ensure
更新;实际应用中,提交代码时,我们可以将
vendor
目录一起提交,此时 其他开发者应该先使用dep check
检验。 但是,一般vendor
目录都比较大,这时可以只提交Gopkg.toml
和Gopkg.lock
文件,开发者在本地使用dep ensure -vendor-only
自行下载依赖包。实现模型
实现模型也有详细说明:Models and Mechanisms,
dep
使用four state system
模型,这是一种经典的包管理模型。这四个模块分别是:Project source code
当前项目代码,即你的项目代码A manifest
依赖清单,这里就是指Gopkg.toml
文件,最初由dep init
生成,主要由用户编辑,来实现个性化需求。它包含几种类型的规则声明来管理 dep 的行为,可以实现灵活的下载依赖包,例如可以指定特定区间版本的依赖包。A lock
依赖描述文件,这里是指Gopkg.lock
文件,里面详细记录了依赖包的地址,hash 值,版本等基本信息,根据描述能够复现完整的依赖图。这个文件完全是由dep
根据Gopkg.toml
和项目引用自动生成的。Source code of the dependences themselves
依赖包自身源码,这是就是指vendor
目录下的代码。dep
主要就是围绕对这四个模块进行输入输出管理实现。流程大概如下:dep
根据项目引用代码和Gopkg.toml
文件内的规则信息,计算自动生成Gopkg.lock
文件,此时,依赖的包信息就确定了,然后根据这些信息将对应的包下载到vendor
目录内。踩过的坑
对于 go 这种必须将项目放在
$GOPATH/src
的约定,实际应用中可能并不灵活,于是有两个技巧:$GOPATH/src
下创建一个软链接,指向项目目录;但是,当使用软链接这种方式的时候,
vendor
目录会失效,变成普通的目录了,因为vendor
目录只有位于$GOPATH
路径下,Go 才会把它当作特殊目录处理。而符号链接只是创建了指向文件名的符号,实际的文件位置依然没变。实际上对于内部包,我们在引用时可以使用相对路径(相对
import
代码的路径,例如:./a/b
或者../x/y
等),这可能是 go 命令的潜规则,这种方式相对较灵活而且直观,项目存放位置也可以很随意。所以对于简单项目,个人是比较喜欢使用这种方式的。但是,这会影响
dep
生成依赖关系,导致dep
误以为没有依赖的外部包,非常奇怪的问题。其实,这两个坑都是因为我之前比较随意,没有按规范,将项目放在
$GOPATH/src
导致的。Go Modules
dep
还没有应用推广,在 Go 1.11 中,又推出了全新的依赖包管理机制Go Modules
,原来叫vgo
,在 Go 1.11 被采纳合并到了主分支,正式被发布,不出意外的话,后续版本应该都会将这个当作包依赖管理的官方解决方案。模块为
GOPATH
提供了替代方案,用来为项目定位依赖和管理版本化。如果 go 命令在$GOPATH/src
之外的目录中运行,并且该目录中有一个模块的话,那么模块功能就会启用,否则 go 将会使用 GOPATH(Go 1.11)。与dep
不同,模块是集成在 go 命令的,可以使用go mod init
创建。小结
Go 作为比较新的语言,包依赖管理方式也在持续变动,主要分为三个阶段:
纯依赖包源码拷贝 --> 基于 vendor --> Go Modules
。每次变动都使管理变的更先进,这对于 Go 和 Gopher 都是有益的,特别是
Go Modules
据称使用了其它语言包管理工具不同的理念算法,看起来比较复杂。因为刚出来,有较多问题需要讨论,后续比较成熟了再研究,可能要等到 Go 1.12 版本吧。所以,如果不想成为第一个吃螃蟹的人,目前,还是使用
dep
作为管理工具比较稳妥。